Index: src/mlx/acars.py
===================================================================
--- src/mlx/acars.py	(revision 139)
+++ src/mlx/acars.py	(revision 139)
@@ -0,0 +1,45 @@
+# Module for ACARS handling
+
+#------------------------------------------------------------------------------
+
+import const
+
+#------------------------------------------------------------------------------
+
+class ACARS(object):
+    """The ACARS object."""
+    def __init__(self, gui, state):
+        """Construct the acars."""
+        self.pid = gui.config.pilotID
+        self.pilotName = gui.loginResult.pilotName
+
+        flight = gui.flight
+        aircraft = flight.aircraft
+
+        self.state = state
+        self.bookedFlight = gui.bookedFlight
+        
+        self.blockTime = 0 if flight.blockTimeStart is None \
+                         else aircraft.state.timestamp - flight.blockTimeStart
+        self.stage = flight.stage
+
+    def getBlockTimeText(self):
+        """Get the block time in HH:MM format"""
+        hours = int(self.blockTime / 3600)
+        minutes = int((self.blockTime / 60)) % 60
+        return "%02d:%02d" % (hours, minutes)
+
+    def getEventText(self):
+        """Get the 'event', i.e. the stage."""
+        if self.stage==const.STAGE_BOARDING: return "Boarding"
+        elif self.stage==const.STAGE_PUSHANDTAXI: return "Taxiing"
+        elif self.stage==const.STAGE_TAKEOFF: return "Departing"
+        elif self.stage==const.STAGE_CLIMB: return "Climbing"
+        elif self.stage==const.STAGE_CRUISE: return "Cruising"
+        elif self.stage==const.STAGE_DESCENT:
+            return "Descending" if self.state.altitude>=10000 \
+                   else "Approaching"
+        else:
+            return "Landed"
+        
+#------------------------------------------------------------------------------
Index: src/mlx/acars.py
===================================================================
--- src/mlx/acars.py	(revision 139)
+++ 	(revision )
@@ -1,45 +1,0 @@
-# Module for ACARS handling
-
-#------------------------------------------------------------------------------
-
-import const
-
-#------------------------------------------------------------------------------
-
-class ACARS(object):
-    """The ACARS object."""
-    def __init__(self, gui, state):
-        """Construct the acars."""
-        self.pid = gui.config.pilotID
-        self.pilotName = gui.loginResult.pilotName
-
-        flight = gui.flight
-        aircraft = flight.aircraft
-
-        self.state = state
-        self.bookedFlight = gui.bookedFlight
-        
-        self.blockTime = 0 if flight.blockTimeStart is None \
-                         else aircraft.state.timestamp - flight.blockTimeStart
-        self.stage = flight.stage
-
-    def getBlockTimeText(self):
-        """Get the block time in HH:MM format"""
-        hours = int(self.blockTime / 3600)
-        minutes = int((self.blockTime / 60)) % 60
-        return "%02d:%02d" % (hours, minutes)
-
-    def getEventText(self):
-        """Get the 'event', i.e. the stage."""
-        if self.stage==const.STAGE_BOARDING: return "Boarding"
-        elif self.stage==const.STAGE_PUSHANDTAXI: return "Taxiing"
-        elif self.stage==const.STAGE_TAKEOFF: return "Departing"
-        elif self.stage==const.STAGE_CLIMB: return "Climbing"
-        elif self.stage==const.STAGE_CRUISE: return "Cruising"
-        elif self.stage==const.STAGE_DESCENT:
-            return "Descending" if self.state.altitude>=10000 \
-                   else "Approaching"
-        else:
-            return "Landed"
-        
-#------------------------------------------------------------------------------
Index: src/mlx/acft.py
===================================================================
--- src/mlx/acft.py	(revision 153)
+++ src/mlx/acft.py	(revision 153)
@@ -0,0 +1,750 @@
+# Module for the simulator-independent aircraft classes
+
+#---------------------------------------------------------------------------------------
+
+import const
+import checks
+import fs
+import util
+
+import sys
+import time
+import traceback
+
+#---------------------------------------------------------------------------------------
+
+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._v1r2LineIndex = None
+        self._vrefLineIndex = None
+
+        self.humanWeight = 82.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())
+
+        if flight.config.isMessageTypeFS(const.MESSAGETYPE_VISIBILITY):
+            self._checkers.append(checks.VisibilityChecker())
+
+        # FIXME: we should have a central data model object, and not collect
+        # the data from the GUI. However, some pieces of data (e.g. V-speeds,
+        # etc. that is entered into the GUI) *should* be a part of the GUI and
+        # queried from it, so the model should have a reference to the GUI as
+        # well and access such data via the GUI!
+        if flight.config.onlineACARS:
+            self._checkers.append(checks.ACARSSender(flight._gui))
+
+        # 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.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
+
+    @property
+    def state(self):
+        """Get the current aircraft state."""
+        return self._aircraftState
+    
+    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:
+            try:
+                checker.check(self._flight, self, self._flight.logger,
+                              self._aircraftState, aircraftState)
+            except:
+                print >> sys.stderr, "Checker", checker, "failed"
+                traceback.print_exc()
+
+        self._flight.handleState(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))
+                flight = self._flight
+                if flight.v1 is None or flight.vr is None or flight.v2 is None:
+                    fs.sendMessage(const.MESSAGETYPE_HELP,
+                                   "Don't forget to set the takeoff V-speeds!",
+                                   5)
+            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._logV1R2()
+            elif newStage==const.STAGE_TAXIAFTERLAND:
+                bookedFlight = self._flight.bookedFlight
+                config = self._flight.config
+                if config.onlineGateSystem and \
+                   bookedFlight.arrivalICAO=="LHBP" and \
+                   config.isMessageTypeFS(const.MESSAGETYPE_GATE_SYSTEM):
+                    self._flight.getFleet(callback = self._fleetRetrieved,
+                                          force = True)
+                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")
+            elif newStage==const.STAGE_END:
+                flightLength = self._flight.flightTimeEnd - self._flight.flightTimeStart
+                self.logger.message(aircraftState.timestamp,
+                                    "Flight time: " +
+                                    util.getTimeIntervalString(flightLength))
+                self.logger.message(aircraftState.timestamp,
+                                    "Flown distance: %.2f NM" % \
+                                    (self._flight.flownDistance,))                
+                blockLength = self._flight.blockTimeEnd - self._flight.blockTimeStart
+                self.logger.message(aircraftState.timestamp,
+                                    "Block time: " +
+                                    util.getTimeIntervalString(blockLength))
+
+    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._logVRef()
+        self.flight.flareStarted(flareStart, flareStartFS)
+        fs.sendMessage(const.MESSAGETYPE_INFORMATION, "Flare-time", 3)
+         
+    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,
+                            "Flaretime: %.3f (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
+
+    def updateV1R2(self):
+        """Update the V1, Vr and V2 values from the flight, if the these values
+        have already been logged."""
+        if self._v1r2LineIndex is not None:
+            self._logV1R2()
+
+    def _logV1R2(self):
+        """Log the V1, Vr and V2 value either newly, or by updating the
+        corresponding line."""
+        message = "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))
+
+        if self._v1r2LineIndex is None:
+            self._v1r2LineIndex = \
+                self.logger.message(self._aircraftState.timestamp, message)
+        else:
+            self.logger.updateLine(self._v1r2LineIndex, message)
+
+    def updateVRef(self):
+        """Update the Vref value from the flight, if the Vref value has already
+        been logged."""
+        if self._vrefLineIndex is not None:
+            self._logVRef()
+
+    def _logVRef(self):
+        """Log the Vref value either newly, or by updating the corresponding
+        line."""
+        message = "VRef speed calculated by the pilot: %s" % \
+                  ("-" if self._flight.vref is None else str(self._flight.vref))
+        if self._vrefLineIndex is None:
+            self._vrefLineIndex = \
+                self.logger.message(self._aircraftState.timestamp, message)
+        else:
+            self.logger.updateLine(self._vrefLineIndex, message)
+
+    def _fleetRetrieved(self, fleet):
+        """Callback for the fleet retrieval result."""
+        if fleet is not None:
+            gateList = ""
+            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
+            for gateNumber in const.lhbpGateNumbers:
+                if gateNumber not in occupiedGateNumbers:
+                    if gateList: gateList += ", "
+                    gateList += gateNumber
+            fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
+                           "Free gates: " + gateList, 20)
+        
+
+#---------------------------------------------------------------------------------------
+
+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: left, centre, right
+    - n1: left, right
+    - reverser: left, right"""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
+    
+    def __init__(self, flight):
+        super(Boeing737, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+
+        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[0], aircraftState.fuel[1],
+                             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: left, right
+    - n1: left, right
+    - reverser: left, right."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_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 - right=%.0f kg" % \
+                            (aircraftState.fuel[0], aircraftState.fuel[1]))
+        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: left, centre, right
+    - n1: left, right
+    - reverser: left, right"""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
+
+    def __init__(self, flight):
+        super(Boeing767, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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[0], aircraftState.fuel[1],
+                             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: left, centre, right
+    - n1: left, right
+    - reverser: left, right."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
+
+    def __init__(self, flight):
+        super(CRJ2, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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[0], aircraftState.fuel[1],
+                             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: left, centre, right
+    - n1: left, right
+    - reverser: left, right."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
+
+    def __init__(self, flight):
+        super(F70, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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[0], aircraftState.fuel[1],
+                             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 aux, left, right, right aux
+    - rpm: left, right
+    - reverser: left, right."""
+    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    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[0], aircraftState.fuel[1],
+                             aircraftState.fuel[2], 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: left tip, left aux, centre, right aux, right tip, external 1,
+    external 2
+    - n1: left, right
+    - reverser: left, right."""
+    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_LEFT_AUX,
+                 const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT_AUX, const.FUELTANK_RIGHT_TIP,
+                 const.FUELTANK_EXTERNAL1, const.FUELTANK_EXTERNAL2]
+
+    def __init__(self, flight):
+        super(T134, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2], 
+                             aircraftState.fuel[4], aircraftState.fuel[3],
+                             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: left aux, left, centre, centre 2, right, right aux
+    - n1: left, centre, right
+    - reverser: left, right"""
+    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
+                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+    def __init__(self, flight):
+        super(T154, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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[0], aircraftState.fuel[1],
+                             aircraftState.fuel[2], aircraftState.fuel[3], 
+                             aircraftState.fuel[4], 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"""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+    
+    def __init__(self, flight):
+        super(YK40, self).__init__(flight)
+        self._checkers.append(checks.ThrustChecker())
+        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),))
+
+#---------------------------------------------------------------------------------------
+
+MostFuelTankAircraft = T134
+
+#---------------------------------------------------------------------------------------
+
+_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 153)
+++ 	(revision )
@@ -1,750 +1,0 @@
-# Module for the simulator-independent aircraft classes
-
-#---------------------------------------------------------------------------------------
-
-import const
-import checks
-import fs
-import util
-
-import sys
-import time
-import traceback
-
-#---------------------------------------------------------------------------------------
-
-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._v1r2LineIndex = None
-        self._vrefLineIndex = None
-
-        self.humanWeight = 82.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())
-
-        if flight.config.isMessageTypeFS(const.MESSAGETYPE_VISIBILITY):
-            self._checkers.append(checks.VisibilityChecker())
-
-        # FIXME: we should have a central data model object, and not collect
-        # the data from the GUI. However, some pieces of data (e.g. V-speeds,
-        # etc. that is entered into the GUI) *should* be a part of the GUI and
-        # queried from it, so the model should have a reference to the GUI as
-        # well and access such data via the GUI!
-        if flight.config.onlineACARS:
-            self._checkers.append(checks.ACARSSender(flight._gui))
-
-        # 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.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
-
-    @property
-    def state(self):
-        """Get the current aircraft state."""
-        return self._aircraftState
-    
-    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:
-            try:
-                checker.check(self._flight, self, self._flight.logger,
-                              self._aircraftState, aircraftState)
-            except:
-                print >> sys.stderr, "Checker", checker, "failed"
-                traceback.print_exc()
-
-        self._flight.handleState(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))
-                flight = self._flight
-                if flight.v1 is None or flight.vr is None or flight.v2 is None:
-                    fs.sendMessage(const.MESSAGETYPE_HELP,
-                                   "Don't forget to set the takeoff V-speeds!",
-                                   5)
-            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._logV1R2()
-            elif newStage==const.STAGE_TAXIAFTERLAND:
-                bookedFlight = self._flight.bookedFlight
-                config = self._flight.config
-                if config.onlineGateSystem and \
-                   bookedFlight.arrivalICAO=="LHBP" and \
-                   config.isMessageTypeFS(const.MESSAGETYPE_GATE_SYSTEM):
-                    self._flight.getFleet(callback = self._fleetRetrieved,
-                                          force = True)
-                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")
-            elif newStage==const.STAGE_END:
-                flightLength = self._flight.flightTimeEnd - self._flight.flightTimeStart
-                self.logger.message(aircraftState.timestamp,
-                                    "Flight time: " +
-                                    util.getTimeIntervalString(flightLength))
-                self.logger.message(aircraftState.timestamp,
-                                    "Flown distance: %.2f NM" % \
-                                    (self._flight.flownDistance,))                
-                blockLength = self._flight.blockTimeEnd - self._flight.blockTimeStart
-                self.logger.message(aircraftState.timestamp,
-                                    "Block time: " +
-                                    util.getTimeIntervalString(blockLength))
-
-    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._logVRef()
-        self.flight.flareStarted(flareStart, flareStartFS)
-        fs.sendMessage(const.MESSAGETYPE_INFORMATION, "Flare-time", 3)
-         
-    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,
-                            "Flaretime: %.3f (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
-
-    def updateV1R2(self):
-        """Update the V1, Vr and V2 values from the flight, if the these values
-        have already been logged."""
-        if self._v1r2LineIndex is not None:
-            self._logV1R2()
-
-    def _logV1R2(self):
-        """Log the V1, Vr and V2 value either newly, or by updating the
-        corresponding line."""
-        message = "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))
-
-        if self._v1r2LineIndex is None:
-            self._v1r2LineIndex = \
-                self.logger.message(self._aircraftState.timestamp, message)
-        else:
-            self.logger.updateLine(self._v1r2LineIndex, message)
-
-    def updateVRef(self):
-        """Update the Vref value from the flight, if the Vref value has already
-        been logged."""
-        if self._vrefLineIndex is not None:
-            self._logVRef()
-
-    def _logVRef(self):
-        """Log the Vref value either newly, or by updating the corresponding
-        line."""
-        message = "VRef speed calculated by the pilot: %s" % \
-                  ("-" if self._flight.vref is None else str(self._flight.vref))
-        if self._vrefLineIndex is None:
-            self._vrefLineIndex = \
-                self.logger.message(self._aircraftState.timestamp, message)
-        else:
-            self.logger.updateLine(self._vrefLineIndex, message)
-
-    def _fleetRetrieved(self, fleet):
-        """Callback for the fleet retrieval result."""
-        if fleet is not None:
-            gateList = ""
-            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
-            for gateNumber in const.lhbpGateNumbers:
-                if gateNumber not in occupiedGateNumbers:
-                    if gateList: gateList += ", "
-                    gateList += gateNumber
-            fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
-                           "Free gates: " + gateList, 20)
-        
-
-#---------------------------------------------------------------------------------------
-
-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: left, centre, right
-    - n1: left, right
-    - reverser: left, right"""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
-    
-    def __init__(self, flight):
-        super(Boeing737, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-
-        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[0], aircraftState.fuel[1],
-                             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: left, right
-    - n1: left, right
-    - reverser: left, right."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_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 - right=%.0f kg" % \
-                            (aircraftState.fuel[0], aircraftState.fuel[1]))
-        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: left, centre, right
-    - n1: left, right
-    - reverser: left, right"""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
-
-    def __init__(self, flight):
-        super(Boeing767, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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[0], aircraftState.fuel[1],
-                             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: left, centre, right
-    - n1: left, right
-    - reverser: left, right."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
-
-    def __init__(self, flight):
-        super(CRJ2, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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[0], aircraftState.fuel[1],
-                             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: left, centre, right
-    - n1: left, right
-    - reverser: left, right."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]    
-
-    def __init__(self, flight):
-        super(F70, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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[0], aircraftState.fuel[1],
-                             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 aux, left, right, right aux
-    - rpm: left, right
-    - reverser: left, right."""
-    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-
-    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[0], aircraftState.fuel[1],
-                             aircraftState.fuel[2], 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: left tip, left aux, centre, right aux, right tip, external 1,
-    external 2
-    - n1: left, right
-    - reverser: left, right."""
-    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_LEFT_AUX,
-                 const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT_AUX, const.FUELTANK_RIGHT_TIP,
-                 const.FUELTANK_EXTERNAL1, const.FUELTANK_EXTERNAL2]
-
-    def __init__(self, flight):
-        super(T134, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2], 
-                             aircraftState.fuel[4], aircraftState.fuel[3],
-                             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: left aux, left, centre, centre 2, right, right aux
-    - n1: left, centre, right
-    - reverser: left, right"""
-    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
-                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-    def __init__(self, flight):
-        super(T154, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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[0], aircraftState.fuel[1],
-                             aircraftState.fuel[2], aircraftState.fuel[3], 
-                             aircraftState.fuel[4], 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"""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
-    
-    def __init__(self, flight):
-        super(YK40, self).__init__(flight)
-        self._checkers.append(checks.ThrustChecker())
-        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),))
-
-#---------------------------------------------------------------------------------------
-
-MostFuelTankAircraft = T134
-
-#---------------------------------------------------------------------------------------
-
-_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 139)
+++ src/mlx/checks.py	(revision 139)
@@ -0,0 +1,995 @@
+# The various checks that may be performed during flight
+
+#---------------------------------------------------------------------------------------
+
+import fs
+import const
+import util
+from acars import ACARS
+
+import time
+
+#---------------------------------------------------------------------------------------
+
+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 ACARSSender(StateChecker):
+    """Sender of online ACARS.
+
+    It sends the ACARS every 3 minutes to the MAVA website."""
+
+    # The interval at which ACARS is sent
+    INTERVAL = 3*60.0
+    
+    def __init__(self, gui):
+        """Construct the ACARS sender."""
+        self._gui = gui
+        self._lastSent = None
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """If the time has come to send the ACARS, send it."""
+        now = time.time()
+        
+        if self._lastSent is not None and \
+           (self._lastSent + ACARSSender.INTERVAL)>now:
+            return 
+
+        acars = ACARS(self._gui, state)
+        self._gui.webHandler.sendACARS(self._acarsCallback, acars)
+
+    def _acarsCallback(self, returned, result):
+        """Callback for ACARS sending."""
+        if returned:
+            print "Sent online ACARS"
+            self._lastSent = time.time() if self._lastSent is None \
+                             else self._lastSent + ACARSSender.INTERVAL
+        else:
+            print "Failed to send the ACARS"
+        
+#---------------------------------------------------------------------------------------
+
+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 VisibilityChecker(StateChecker):
+    """Inform the pilot of the visibility once when descending below 2000 ft,
+    then when descending below 1000 ft."""
+    def __init__(self):
+        """Construct the visibility checker."""
+        self._informedBelow2000 = False
+        self._informedBelow1000 = False
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check if we need to inform the pilot of the visibility."""
+        if flight.stage==const.STAGE_DESCENT or \
+           flight.stage==const.STAGE_LANDING:
+            if (state.radioAltitude<2000 and not self._informedBelow2000) or \
+               (state.radioAltitude<1000 and not self._informedBelow1000):
+                visibilityString = util.visibility2String(state.visibility)
+                fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
+                               "Current visibility: " + visibilityString,
+                               5)
+                logger.message(state.timestamp,
+                               "Pilot was informed about the visibility: " +
+                               visibilityString)
+                self._informedBelow2000 = True
+                self._informedBelow1000 = state.radioAltitude<1000
+
+#---------------------------------------------------------------------------------------
+
+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 139)
+++ 	(revision )
@@ -1,995 +1,0 @@
-# The various checks that may be performed during flight
-
-#---------------------------------------------------------------------------------------
-
-import fs
-import const
-import util
-from acars import ACARS
-
-import time
-
-#---------------------------------------------------------------------------------------
-
-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 ACARSSender(StateChecker):
-    """Sender of online ACARS.
-
-    It sends the ACARS every 3 minutes to the MAVA website."""
-
-    # The interval at which ACARS is sent
-    INTERVAL = 3*60.0
-    
-    def __init__(self, gui):
-        """Construct the ACARS sender."""
-        self._gui = gui
-        self._lastSent = None
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """If the time has come to send the ACARS, send it."""
-        now = time.time()
-        
-        if self._lastSent is not None and \
-           (self._lastSent + ACARSSender.INTERVAL)>now:
-            return 
-
-        acars = ACARS(self._gui, state)
-        self._gui.webHandler.sendACARS(self._acarsCallback, acars)
-
-    def _acarsCallback(self, returned, result):
-        """Callback for ACARS sending."""
-        if returned:
-            print "Sent online ACARS"
-            self._lastSent = time.time() if self._lastSent is None \
-                             else self._lastSent + ACARSSender.INTERVAL
-        else:
-            print "Failed to send the ACARS"
-        
-#---------------------------------------------------------------------------------------
-
-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 VisibilityChecker(StateChecker):
-    """Inform the pilot of the visibility once when descending below 2000 ft,
-    then when descending below 1000 ft."""
-    def __init__(self):
-        """Construct the visibility checker."""
-        self._informedBelow2000 = False
-        self._informedBelow1000 = False
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check if we need to inform the pilot of the visibility."""
-        if flight.stage==const.STAGE_DESCENT or \
-           flight.stage==const.STAGE_LANDING:
-            if (state.radioAltitude<2000 and not self._informedBelow2000) or \
-               (state.radioAltitude<1000 and not self._informedBelow1000):
-                visibilityString = util.visibility2String(state.visibility)
-                fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
-                               "Current visibility: " + visibilityString,
-                               5)
-                logger.message(state.timestamp,
-                               "Pilot was informed about the visibility: " +
-                               visibilityString)
-                self._informedBelow2000 = True
-                self._informedBelow1000 = state.radioAltitude<1000
-
-#---------------------------------------------------------------------------------------
-
-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 156)
+++ src/mlx/config.py	(revision 156)
@@ -0,0 +1,385 @@
+# Configuration and related stuff
+# -*- encoding: utf-8 -*-
+
+#-------------------------------------------------------------------------------
+
+import const
+
+import os
+import sys
+import ConfigParser
+
+#-------------------------------------------------------------------------------
+
+configPath = os.path.join(os.path.expanduser("~"),
+                          "mlx.config" if os.name=="nt" else ".mlxrc")
+
+#-------------------------------------------------------------------------------
+
+if os.name=="nt":
+    _languageMap = { "en_GB" : "eng",
+                     "hu_HU" : "hun" }
+
+#-------------------------------------------------------------------------------
+
+class Config(object):
+    """Our configuration."""
+    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update"
+
+    _messageTypesSection = "messageTypes"
+    
+    def __init__(self):
+        """Construct the configuration with default values."""
+
+        self._pilotID = ""
+        self._password = ""
+        self._rememberPassword = False
+
+        self._language = ""
+        self._hideMinimizedWindow = True
+        self._onlineGateSystem = True
+        self._onlineACARS = True
+        self._flareTimeFromFS = False
+        self._syncFSTime = False
+
+        self._pirepDirectory = None
+        
+        self._autoUpdate = True        
+        self._updateURL = Config.DEFAULT_UPDATE_URL
+
+        self._messageTypeLevels = {}
+        
+        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 language(self):
+        """Get the language to use."""
+        return self._language
+
+    @language.setter
+    def language(self, language):
+        """Set the language to use."""
+        if language!=self._language:
+            self._language = language
+            self._modified = True
+
+    @property
+    def hideMinimizedWindow(self):
+        """Get whether a minimized window should be hidden."""
+        return self._hideMinimizedWindow
+
+    @hideMinimizedWindow.setter
+    def hideMinimizedWindow(self, hideMinimizedWindow):
+        """Set whether a minimized window should be hidden."""
+        if hideMinimizedWindow!=self._hideMinimizedWindow:
+            self._hideMinimizedWindow = hideMinimizedWindow
+            self._modified = True
+    
+    @property
+    def onlineGateSystem(self):
+        """Get whether the online gate system should be used."""
+        return self._onlineGateSystem
+
+    @onlineGateSystem.setter
+    def onlineGateSystem(self, onlineGateSystem):
+        """Set whether the online gate system should be used."""
+        if onlineGateSystem!=self._onlineGateSystem:
+            self._onlineGateSystem = onlineGateSystem
+            self._modified = True
+
+    @property
+    def onlineACARS(self):
+        """Get whether the online ACARS system should be used."""
+        return self._onlineACARS
+
+    @onlineACARS.setter
+    def onlineACARS(self, onlineACARS):
+        """Set whether the online ACARS system should be used."""
+        if onlineACARS!=self._onlineACARS:
+            self._onlineACARS = onlineACARS
+            self._modified = True
+
+    @property
+    def flareTimeFromFS(self):
+        """Get whether the flare time should be calculated from the time values
+        returned by the simulator."""
+        return self._flareTimeFromFS
+
+    @flareTimeFromFS.setter
+    def flareTimeFromFS(self, flareTimeFromFS):
+        """Set whether the flare time should be calculated from the time values
+        returned by the simulator."""
+        if flareTimeFromFS!=self._flareTimeFromFS:
+            self._flareTimeFromFS = flareTimeFromFS
+            self._modified = True
+
+    @property
+    def syncFSTime(self):
+        """Get whether the simulator's time should be synchronized with the
+        machine's clock."""
+        return self._syncFSTime
+
+    @syncFSTime.setter
+    def syncFSTime(self, syncFSTime):
+        """Set whether the simulator's time should be synchronized with the
+        machine's clock."""
+        if syncFSTime!=self._syncFSTime:
+            self._syncFSTime = syncFSTime
+            self._modified = True
+
+    @property
+    def pirepDirectory(self):
+        """Get the directory offered by default when saving a PIREP."""
+        return self._pirepDirectory
+
+    @pirepDirectory.setter
+    def pirepDirectory(self, pirepDirectory):
+        """Get the directory offered by default when saving a PIREP."""
+        if pirepDirectory!=self._pirepDirectory and \
+           (pirepDirectory!="" or self._pirepDirectory is not None):
+            self._pirepDirectory = None if pirepDirectory=="" \
+                                   else pirepDirectory
+            self._modified = True
+
+    def getMessageTypeLevel(self, messageType):
+        """Get the level for the given message type."""
+        return self._messageTypeLevels[messageType] \
+               if messageType in self._messageTypeLevels \
+               else const.MESSAGELEVEL_NONE
+
+    def isMessageTypeFS(self, messageType):
+        """Determine if the given message type is displayed in the
+        simulator."""
+        level = self.getMessageTypeLevel(messageType)
+        return level==const.MESSAGELEVEL_FS or \
+               level==const.MESSAGELEVEL_BOTH
+        
+    def setMessageTypeLevel(self, messageType, level):
+        """Set the level of the given message type."""
+        if messageType not in self._messageTypeLevels or \
+           self._messageTypeLevels[messageType]!=level:
+            self._messageTypeLevels[messageType] = level
+            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._language = self._get(config, "general", "language", "")
+        self._hideMinimizedWindow = self._getBoolean(config, "general",
+                                                     "hideMinimizedWindow",
+                                                     True)
+        self._onlineGateSystem = self._getBoolean(config, "general",
+                                                  "onlineGateSystem",
+                                                  True)
+        self._onlineACARS = self._getBoolean(config, "general",
+                                             "onlineACARS", True)
+        self._flareTimeFromFS = self._getBoolean(config, "general",
+                                                 "flareTimeFromFS",
+                                                 False)
+        self._syncFSTime = self._getBoolean(config, "general",
+                                            "syncFSTime",
+                                            False)
+        self._pirepDirectory = self._get(config, "general",
+                                         "pirepDirectory", None)
+
+        self._messageTypeLevels = {}
+        for messageType in const.messageTypes:
+            self._messageTypeLevels[messageType] = \
+                self._getMessageTypeLevel(config, messageType)
+            
+        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("general")
+        if self._language:
+            config.set("general", "language", self._language)
+        config.set("general", "hideMinimizedWindow",
+                   "yes" if self._hideMinimizedWindow else "no")
+        config.set("general", "onlineGateSystem",
+                   "yes" if self._onlineGateSystem else "no")
+        config.set("general", "onlineACARS",
+                   "yes" if self._onlineACARS else "no")
+        config.set("general", "flareTimeFromFS",
+                   "yes" if self._flareTimeFromFS else "no")
+        config.set("general", "syncFSTime",
+                   "yes" if self._syncFSTime else "no")
+
+        if self._pirepDirectory is not None:
+            config.set("general", "pirepDirectory", self._pirepDirectory)
+
+        config.add_section(Config._messageTypesSection)
+        for messageType in const.messageTypes:
+            if messageType in self._messageTypeLevels:
+                option = self._getMessageTypeLevelOptionName(messageType)
+                level = self._messageTypeLevels[messageType]                
+                config.set(Config._messageTypesSection, option,
+                           const.messageLevel2string(level))
+        
+        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
+
+    def _getMessageTypeLevel(self, config, messageType):
+        """Get the message type level for the given message type."""
+        option = self._getMessageTypeLevelOptionName(messageType)
+        if config.has_option(Config._messageTypesSection, option):
+            value = config.get(Config._messageTypesSection, option)
+            return const.string2messageLevel(value)
+        elif messageType in [const.MESSAGETYPE_LOGGER_ERROR,
+                             const.MESSAGETYPE_FAULT,
+                             const.MESSAGETYPE_NOGO,
+                             const.MESSAGETYPE_GATE_SYSTEM,
+                             const.MESSAGETYPE_HELP]:            
+            return const.MESSAGELEVEL_BOTH
+        else:
+            return const.MESSAGELEVEL_FS
+
+    def _getMessageTypeLevelOptionName(self, messageType):
+        """Get the option name for the given message type level."""
+        return const.messageType2string(messageType)
+
+    def setupLocale(self):
+        """Setup the locale based on the language set.
+
+        Return True if a specific language was set, False otherwise."""
+        import locale
+        if self._language:
+            print "Setting up locale for", self._language
+            os.environ["LANGUAGE"] = self._language
+            langAndEncoding = self._language + "." + locale.getpreferredencoding()
+            os.environ["LANG"] = langAndEncoding
+            os.environ["LC_MESSAGES"] = langAndEncoding
+            os.environ["LC_COLLATE"] = langAndEncoding
+            os.environ["LC_CTYPE"] = langAndEncoding
+            os.environ["LC_MONETARY"] = langAndEncoding
+            os.environ["LC_NUMERIC"] = langAndEncoding
+            os.environ["LC_TIME"] = langAndEncoding
+            return True
+        else:
+            return False
+
+    def getLanguage(self):
+        """Get the language to be used."""
+        import locale
+        if self._language:
+            if os.name=="nt":
+                if self._language in _languageMap:
+                    locale.setlocale(locale.LC_ALL, _languageMap[self._language])
+                else:
+                    locale.setlocale(locale.LC_ALL, "")
+            else:
+                locale.setlocale(locale.LC_ALL, (self._language,
+                                                 locale.getpreferredencoding()))
+            return self._language
+        else:
+            locale.setlocale(locale.LC_ALL, "")
+            return locale.getdefaultlocale()[0]
+
+#-------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 156)
+++ 	(revision )
@@ -1,385 +1,0 @@
-# Configuration and related stuff
-# -*- encoding: utf-8 -*-
-
-#-------------------------------------------------------------------------------
-
-import const
-
-import os
-import sys
-import ConfigParser
-
-#-------------------------------------------------------------------------------
-
-configPath = os.path.join(os.path.expanduser("~"),
-                          "mlx.config" if os.name=="nt" else ".mlxrc")
-
-#-------------------------------------------------------------------------------
-
-if os.name=="nt":
-    _languageMap = { "en_GB" : "eng",
-                     "hu_HU" : "hun" }
-
-#-------------------------------------------------------------------------------
-
-class Config(object):
-    """Our configuration."""
-    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update"
-
-    _messageTypesSection = "messageTypes"
-    
-    def __init__(self):
-        """Construct the configuration with default values."""
-
-        self._pilotID = ""
-        self._password = ""
-        self._rememberPassword = False
-
-        self._language = ""
-        self._hideMinimizedWindow = True
-        self._onlineGateSystem = True
-        self._onlineACARS = True
-        self._flareTimeFromFS = False
-        self._syncFSTime = False
-
-        self._pirepDirectory = None
-        
-        self._autoUpdate = True        
-        self._updateURL = Config.DEFAULT_UPDATE_URL
-
-        self._messageTypeLevels = {}
-        
-        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 language(self):
-        """Get the language to use."""
-        return self._language
-
-    @language.setter
-    def language(self, language):
-        """Set the language to use."""
-        if language!=self._language:
-            self._language = language
-            self._modified = True
-
-    @property
-    def hideMinimizedWindow(self):
-        """Get whether a minimized window should be hidden."""
-        return self._hideMinimizedWindow
-
-    @hideMinimizedWindow.setter
-    def hideMinimizedWindow(self, hideMinimizedWindow):
-        """Set whether a minimized window should be hidden."""
-        if hideMinimizedWindow!=self._hideMinimizedWindow:
-            self._hideMinimizedWindow = hideMinimizedWindow
-            self._modified = True
-    
-    @property
-    def onlineGateSystem(self):
-        """Get whether the online gate system should be used."""
-        return self._onlineGateSystem
-
-    @onlineGateSystem.setter
-    def onlineGateSystem(self, onlineGateSystem):
-        """Set whether the online gate system should be used."""
-        if onlineGateSystem!=self._onlineGateSystem:
-            self._onlineGateSystem = onlineGateSystem
-            self._modified = True
-
-    @property
-    def onlineACARS(self):
-        """Get whether the online ACARS system should be used."""
-        return self._onlineACARS
-
-    @onlineACARS.setter
-    def onlineACARS(self, onlineACARS):
-        """Set whether the online ACARS system should be used."""
-        if onlineACARS!=self._onlineACARS:
-            self._onlineACARS = onlineACARS
-            self._modified = True
-
-    @property
-    def flareTimeFromFS(self):
-        """Get whether the flare time should be calculated from the time values
-        returned by the simulator."""
-        return self._flareTimeFromFS
-
-    @flareTimeFromFS.setter
-    def flareTimeFromFS(self, flareTimeFromFS):
-        """Set whether the flare time should be calculated from the time values
-        returned by the simulator."""
-        if flareTimeFromFS!=self._flareTimeFromFS:
-            self._flareTimeFromFS = flareTimeFromFS
-            self._modified = True
-
-    @property
-    def syncFSTime(self):
-        """Get whether the simulator's time should be synchronized with the
-        machine's clock."""
-        return self._syncFSTime
-
-    @syncFSTime.setter
-    def syncFSTime(self, syncFSTime):
-        """Set whether the simulator's time should be synchronized with the
-        machine's clock."""
-        if syncFSTime!=self._syncFSTime:
-            self._syncFSTime = syncFSTime
-            self._modified = True
-
-    @property
-    def pirepDirectory(self):
-        """Get the directory offered by default when saving a PIREP."""
-        return self._pirepDirectory
-
-    @pirepDirectory.setter
-    def pirepDirectory(self, pirepDirectory):
-        """Get the directory offered by default when saving a PIREP."""
-        if pirepDirectory!=self._pirepDirectory and \
-           (pirepDirectory!="" or self._pirepDirectory is not None):
-            self._pirepDirectory = None if pirepDirectory=="" \
-                                   else pirepDirectory
-            self._modified = True
-
-    def getMessageTypeLevel(self, messageType):
-        """Get the level for the given message type."""
-        return self._messageTypeLevels[messageType] \
-               if messageType in self._messageTypeLevels \
-               else const.MESSAGELEVEL_NONE
-
-    def isMessageTypeFS(self, messageType):
-        """Determine if the given message type is displayed in the
-        simulator."""
-        level = self.getMessageTypeLevel(messageType)
-        return level==const.MESSAGELEVEL_FS or \
-               level==const.MESSAGELEVEL_BOTH
-        
-    def setMessageTypeLevel(self, messageType, level):
-        """Set the level of the given message type."""
-        if messageType not in self._messageTypeLevels or \
-           self._messageTypeLevels[messageType]!=level:
-            self._messageTypeLevels[messageType] = level
-            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._language = self._get(config, "general", "language", "")
-        self._hideMinimizedWindow = self._getBoolean(config, "general",
-                                                     "hideMinimizedWindow",
-                                                     True)
-        self._onlineGateSystem = self._getBoolean(config, "general",
-                                                  "onlineGateSystem",
-                                                  True)
-        self._onlineACARS = self._getBoolean(config, "general",
-                                             "onlineACARS", True)
-        self._flareTimeFromFS = self._getBoolean(config, "general",
-                                                 "flareTimeFromFS",
-                                                 False)
-        self._syncFSTime = self._getBoolean(config, "general",
-                                            "syncFSTime",
-                                            False)
-        self._pirepDirectory = self._get(config, "general",
-                                         "pirepDirectory", None)
-
-        self._messageTypeLevels = {}
-        for messageType in const.messageTypes:
-            self._messageTypeLevels[messageType] = \
-                self._getMessageTypeLevel(config, messageType)
-            
-        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("general")
-        if self._language:
-            config.set("general", "language", self._language)
-        config.set("general", "hideMinimizedWindow",
-                   "yes" if self._hideMinimizedWindow else "no")
-        config.set("general", "onlineGateSystem",
-                   "yes" if self._onlineGateSystem else "no")
-        config.set("general", "onlineACARS",
-                   "yes" if self._onlineACARS else "no")
-        config.set("general", "flareTimeFromFS",
-                   "yes" if self._flareTimeFromFS else "no")
-        config.set("general", "syncFSTime",
-                   "yes" if self._syncFSTime else "no")
-
-        if self._pirepDirectory is not None:
-            config.set("general", "pirepDirectory", self._pirepDirectory)
-
-        config.add_section(Config._messageTypesSection)
-        for messageType in const.messageTypes:
-            if messageType in self._messageTypeLevels:
-                option = self._getMessageTypeLevelOptionName(messageType)
-                level = self._messageTypeLevels[messageType]                
-                config.set(Config._messageTypesSection, option,
-                           const.messageLevel2string(level))
-        
-        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
-
-    def _getMessageTypeLevel(self, config, messageType):
-        """Get the message type level for the given message type."""
-        option = self._getMessageTypeLevelOptionName(messageType)
-        if config.has_option(Config._messageTypesSection, option):
-            value = config.get(Config._messageTypesSection, option)
-            return const.string2messageLevel(value)
-        elif messageType in [const.MESSAGETYPE_LOGGER_ERROR,
-                             const.MESSAGETYPE_FAULT,
-                             const.MESSAGETYPE_NOGO,
-                             const.MESSAGETYPE_GATE_SYSTEM,
-                             const.MESSAGETYPE_HELP]:            
-            return const.MESSAGELEVEL_BOTH
-        else:
-            return const.MESSAGELEVEL_FS
-
-    def _getMessageTypeLevelOptionName(self, messageType):
-        """Get the option name for the given message type level."""
-        return const.messageType2string(messageType)
-
-    def setupLocale(self):
-        """Setup the locale based on the language set.
-
-        Return True if a specific language was set, False otherwise."""
-        import locale
-        if self._language:
-            print "Setting up locale for", self._language
-            os.environ["LANGUAGE"] = self._language
-            langAndEncoding = self._language + "." + locale.getpreferredencoding()
-            os.environ["LANG"] = langAndEncoding
-            os.environ["LC_MESSAGES"] = langAndEncoding
-            os.environ["LC_COLLATE"] = langAndEncoding
-            os.environ["LC_CTYPE"] = langAndEncoding
-            os.environ["LC_MONETARY"] = langAndEncoding
-            os.environ["LC_NUMERIC"] = langAndEncoding
-            os.environ["LC_TIME"] = langAndEncoding
-            return True
-        else:
-            return False
-
-    def getLanguage(self):
-        """Get the language to be used."""
-        import locale
-        if self._language:
-            if os.name=="nt":
-                if self._language in _languageMap:
-                    locale.setlocale(locale.LC_ALL, _languageMap[self._language])
-                else:
-                    locale.setlocale(locale.LC_ALL, "")
-            else:
-                locale.setlocale(locale.LC_ALL, (self._language,
-                                                 locale.getpreferredencoding()))
-            return self._language
-        else:
-            locale.setlocale(locale.LC_ALL, "")
-            return locale.getdefaultlocale()[0]
-
-#-------------------------------------------------------------------------------
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 141)
+++ src/mlx/const.py	(revision 141)
@@ -0,0 +1,389 @@
+# Various constants used in the logger
+
+#-------------------------------------------------------------------------------
+
+# The version of the program
+VERSION="0.03"
+
+#-------------------------------------------------------------------------------
+
+# 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
+
+#-------------------------------------------------------------------------------
+
+_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
+        
+#-------------------------------------------------------------------------------
+
+# 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
+
+#-------------------------------------------------------------------------------
+
+# Flight type: scheduled
+FLIGHTTYPE_SCHEDULED = 0
+
+# Flight type: old-timer
+FLIGHTTYPE_OLDTIMER = 1
+
+# Flight type: VIP
+FLIGHTTYPE_VIP = 2
+
+# Flight type: charter
+FLIGHTTYPE_CHARTER = 3
+
+#-------------------------------------------------------------------------------
+
+# Delay code: loading problems
+DELAYCODE_LOADING = 0
+
+# Delay code: VATSIM problem
+DELAYCODE_VATSIM = 1
+
+# Delay code: network problems
+DELAYCODE_NETWORK = 2
+
+# Delay code: controller's fault
+DELAYCODE_CONTROLLER = 3
+
+# Delay code: system crash or freeze
+DELAYCODE_SYSTEM = 4
+
+# Delay code: navigation problem
+DELAYCODE_NAVIGATION = 5
+
+# Delay code: traffic problems
+DELAYCODE_TRAFFIC = 6
+
+# Delay code: apron navigation
+DELAYCODE_APRON = 7
+
+# Delay code: weather problems
+DELAYCODE_WEATHER = 8
+
+# Delay code: personal reasons
+DELAYCODE_PERSONAL = 9
+
+#-------------------------------------------------------------------------------
+
+# Message type: logger error
+# FIXME: cannot set the hotkey
+MESSAGETYPE_LOGGER_ERROR = 1
+
+# Message type: information
+MESSAGETYPE_INFORMATION = 2
+
+# Message type: fault messages
+MESSAGETYPE_FAULT = 3
+
+# Message type: NO-GO fault messages
+MESSAGETYPE_NOGO = 4
+
+# Message type: gate system messages
+MESSAGETYPE_GATE_SYSTEM = 5
+
+# Message type: environment messages
+# FIXME: flight plan closed (5 sec)
+MESSAGETYPE_ENVIRONMENT = 6
+
+# Message type: help messages
+MESSAGETYPE_HELP = 7
+
+# Message type: visibility messages
+MESSAGETYPE_VISIBILITY = 8
+
+#-------------------------------------------------------------------------------
+
+messageTypes = [ MESSAGETYPE_LOGGER_ERROR,
+                 MESSAGETYPE_INFORMATION,
+                 MESSAGETYPE_FAULT,
+                 MESSAGETYPE_NOGO,
+                 MESSAGETYPE_GATE_SYSTEM,
+                 MESSAGETYPE_ENVIRONMENT,
+                 MESSAGETYPE_HELP,
+                 MESSAGETYPE_VISIBILITY ]
+
+#-------------------------------------------------------------------------------
+
+_messageTypeStrings = { MESSAGETYPE_LOGGER_ERROR : "loggerError",
+                        MESSAGETYPE_INFORMATION : "information",
+                        MESSAGETYPE_FAULT : "fault",
+                        MESSAGETYPE_NOGO : "nogo",
+                        MESSAGETYPE_GATE_SYSTEM : "gateSystem",
+                        MESSAGETYPE_ENVIRONMENT : "environment",
+                        MESSAGETYPE_HELP : "help",
+                        MESSAGETYPE_VISIBILITY : "visibility" }
+
+def messageType2string(messageType):
+    """Get the string equivalent of the given message type."""
+    return _messageTypeStrings[messageType] \
+           if messageType in _messageTypeStrings else None    
+
+#-------------------------------------------------------------------------------
+
+# Message display level: none
+MESSAGELEVEL_NONE = 0
+
+# Message display level: only message in the simulator
+MESSAGELEVEL_FS = 1
+
+# Message display level: only sound
+MESSAGELEVEL_SOUND = 2
+
+# Message display level: both
+MESSAGELEVEL_BOTH = 3
+
+#-------------------------------------------------------------------------------
+
+messageLevels = [ MESSAGELEVEL_NONE,
+                  MESSAGELEVEL_FS,
+                  MESSAGELEVEL_SOUND,
+                  MESSAGELEVEL_BOTH ]
+
+#-------------------------------------------------------------------------------
+
+_messageLevelStrings = { MESSAGELEVEL_NONE : "none",
+                         MESSAGELEVEL_FS : "fs",
+                         MESSAGELEVEL_SOUND : "sound",
+                         MESSAGELEVEL_BOTH : "both" }
+
+def messageLevel2string(messageLevel):
+    """Get the string equivalent of the given message level."""
+    return _messageLevelStrings[messageLevel] \
+           if messageLevel in _messageLevelStrings else None    
+
+def string2messageLevel(str):
+    """Get the message level for the given string."""
+    for (value, s) in _messageLevelStrings.iteritems():
+        if str==s:
+            return value
+    return MESSAGELEVEL_NONE
+
+#-------------------------------------------------------------------------------
+
+# Sound: ding
+SOUND_DING = "ding.wav"
+
+#-------------------------------------------------------------------------------
+
+# Fuel tank: centre
+FUELTANK_CENTRE = 1
+
+# Fuel tank: left
+FUELTANK_LEFT = 2
+
+# Fuel tank: right
+FUELTANK_RIGHT = 3
+
+# Fuel tank: left aux
+FUELTANK_LEFT_AUX = 4
+
+# Fuel tank: right aux
+FUELTANK_RIGHT_AUX = 5
+
+# Fuel tank: left tip
+FUELTANK_LEFT_TIP = 6
+
+# Fuel tank: right tip
+FUELTANK_RIGHT_TIP = 7
+
+# Fuel tank: external 1
+FUELTANK_EXTERNAL1 = 8
+
+# Fuel tank: external 2
+FUELTANK_EXTERNAL2 = 9
+
+# Fuel tank: centre2
+FUELTANK_CENTRE2 = 10
+
+#-------------------------------------------------------------------------------
+
+fuelTanks = [ FUELTANK_CENTRE,
+              FUELTANK_LEFT,
+              FUELTANK_RIGHT,
+              FUELTANK_LEFT_AUX,
+              FUELTANK_RIGHT_AUX,
+              FUELTANK_LEFT_TIP,
+              FUELTANK_RIGHT_TIP,
+              FUELTANK_EXTERNAL1,
+              FUELTANK_EXTERNAL2,
+              FUELTANK_CENTRE2 ]
+
+#-------------------------------------------------------------------------------
+
+_fuelTankStrings = { FUELTANK_CENTRE : "centre",
+                     FUELTANK_LEFT : "left",
+                     FUELTANK_RIGHT : "right",
+                     FUELTANK_LEFT_AUX : "left_aux",
+                     FUELTANK_RIGHT_AUX : "right_aux",
+                     FUELTANK_LEFT_TIP : "left_tip",
+                     FUELTANK_RIGHT_TIP : "right_tip",
+                     FUELTANK_EXTERNAL1 : "external1",
+                     FUELTANK_EXTERNAL2 : "external2",
+                     FUELTANK_CENTRE2 : "centre2" }
+
+def fuelTank2string(fuelTank):
+    """Get the string equivalent of the given fuelTank."""
+    return _fuelTankStrings[fuelTank] \
+           if fuelTank in _fuelTankStrings else None    
+
+#-------------------------------------------------------------------------------
+
+# 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))
+
+#-------------------------------------------------------------------------------
+
+languages = ["$system", "en_GB", "hu_HU"]
+
+#-------------------------------------------------------------------------------
+
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 141)
+++ 	(revision )
@@ -1,389 +1,0 @@
-# Various constants used in the logger
-
-#-------------------------------------------------------------------------------
-
-# The version of the program
-VERSION="0.03"
-
-#-------------------------------------------------------------------------------
-
-# 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
-
-#-------------------------------------------------------------------------------
-
-_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
-        
-#-------------------------------------------------------------------------------
-
-# 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
-
-#-------------------------------------------------------------------------------
-
-# Flight type: scheduled
-FLIGHTTYPE_SCHEDULED = 0
-
-# Flight type: old-timer
-FLIGHTTYPE_OLDTIMER = 1
-
-# Flight type: VIP
-FLIGHTTYPE_VIP = 2
-
-# Flight type: charter
-FLIGHTTYPE_CHARTER = 3
-
-#-------------------------------------------------------------------------------
-
-# Delay code: loading problems
-DELAYCODE_LOADING = 0
-
-# Delay code: VATSIM problem
-DELAYCODE_VATSIM = 1
-
-# Delay code: network problems
-DELAYCODE_NETWORK = 2
-
-# Delay code: controller's fault
-DELAYCODE_CONTROLLER = 3
-
-# Delay code: system crash or freeze
-DELAYCODE_SYSTEM = 4
-
-# Delay code: navigation problem
-DELAYCODE_NAVIGATION = 5
-
-# Delay code: traffic problems
-DELAYCODE_TRAFFIC = 6
-
-# Delay code: apron navigation
-DELAYCODE_APRON = 7
-
-# Delay code: weather problems
-DELAYCODE_WEATHER = 8
-
-# Delay code: personal reasons
-DELAYCODE_PERSONAL = 9
-
-#-------------------------------------------------------------------------------
-
-# Message type: logger error
-# FIXME: cannot set the hotkey
-MESSAGETYPE_LOGGER_ERROR = 1
-
-# Message type: information
-MESSAGETYPE_INFORMATION = 2
-
-# Message type: fault messages
-MESSAGETYPE_FAULT = 3
-
-# Message type: NO-GO fault messages
-MESSAGETYPE_NOGO = 4
-
-# Message type: gate system messages
-MESSAGETYPE_GATE_SYSTEM = 5
-
-# Message type: environment messages
-# FIXME: flight plan closed (5 sec)
-MESSAGETYPE_ENVIRONMENT = 6
-
-# Message type: help messages
-MESSAGETYPE_HELP = 7
-
-# Message type: visibility messages
-MESSAGETYPE_VISIBILITY = 8
-
-#-------------------------------------------------------------------------------
-
-messageTypes = [ MESSAGETYPE_LOGGER_ERROR,
-                 MESSAGETYPE_INFORMATION,
-                 MESSAGETYPE_FAULT,
-                 MESSAGETYPE_NOGO,
-                 MESSAGETYPE_GATE_SYSTEM,
-                 MESSAGETYPE_ENVIRONMENT,
-                 MESSAGETYPE_HELP,
-                 MESSAGETYPE_VISIBILITY ]
-
-#-------------------------------------------------------------------------------
-
-_messageTypeStrings = { MESSAGETYPE_LOGGER_ERROR : "loggerError",
-                        MESSAGETYPE_INFORMATION : "information",
-                        MESSAGETYPE_FAULT : "fault",
-                        MESSAGETYPE_NOGO : "nogo",
-                        MESSAGETYPE_GATE_SYSTEM : "gateSystem",
-                        MESSAGETYPE_ENVIRONMENT : "environment",
-                        MESSAGETYPE_HELP : "help",
-                        MESSAGETYPE_VISIBILITY : "visibility" }
-
-def messageType2string(messageType):
-    """Get the string equivalent of the given message type."""
-    return _messageTypeStrings[messageType] \
-           if messageType in _messageTypeStrings else None    
-
-#-------------------------------------------------------------------------------
-
-# Message display level: none
-MESSAGELEVEL_NONE = 0
-
-# Message display level: only message in the simulator
-MESSAGELEVEL_FS = 1
-
-# Message display level: only sound
-MESSAGELEVEL_SOUND = 2
-
-# Message display level: both
-MESSAGELEVEL_BOTH = 3
-
-#-------------------------------------------------------------------------------
-
-messageLevels = [ MESSAGELEVEL_NONE,
-                  MESSAGELEVEL_FS,
-                  MESSAGELEVEL_SOUND,
-                  MESSAGELEVEL_BOTH ]
-
-#-------------------------------------------------------------------------------
-
-_messageLevelStrings = { MESSAGELEVEL_NONE : "none",
-                         MESSAGELEVEL_FS : "fs",
-                         MESSAGELEVEL_SOUND : "sound",
-                         MESSAGELEVEL_BOTH : "both" }
-
-def messageLevel2string(messageLevel):
-    """Get the string equivalent of the given message level."""
-    return _messageLevelStrings[messageLevel] \
-           if messageLevel in _messageLevelStrings else None    
-
-def string2messageLevel(str):
-    """Get the message level for the given string."""
-    for (value, s) in _messageLevelStrings.iteritems():
-        if str==s:
-            return value
-    return MESSAGELEVEL_NONE
-
-#-------------------------------------------------------------------------------
-
-# Sound: ding
-SOUND_DING = "ding.wav"
-
-#-------------------------------------------------------------------------------
-
-# Fuel tank: centre
-FUELTANK_CENTRE = 1
-
-# Fuel tank: left
-FUELTANK_LEFT = 2
-
-# Fuel tank: right
-FUELTANK_RIGHT = 3
-
-# Fuel tank: left aux
-FUELTANK_LEFT_AUX = 4
-
-# Fuel tank: right aux
-FUELTANK_RIGHT_AUX = 5
-
-# Fuel tank: left tip
-FUELTANK_LEFT_TIP = 6
-
-# Fuel tank: right tip
-FUELTANK_RIGHT_TIP = 7
-
-# Fuel tank: external 1
-FUELTANK_EXTERNAL1 = 8
-
-# Fuel tank: external 2
-FUELTANK_EXTERNAL2 = 9
-
-# Fuel tank: centre2
-FUELTANK_CENTRE2 = 10
-
-#-------------------------------------------------------------------------------
-
-fuelTanks = [ FUELTANK_CENTRE,
-              FUELTANK_LEFT,
-              FUELTANK_RIGHT,
-              FUELTANK_LEFT_AUX,
-              FUELTANK_RIGHT_AUX,
-              FUELTANK_LEFT_TIP,
-              FUELTANK_RIGHT_TIP,
-              FUELTANK_EXTERNAL1,
-              FUELTANK_EXTERNAL2,
-              FUELTANK_CENTRE2 ]
-
-#-------------------------------------------------------------------------------
-
-_fuelTankStrings = { FUELTANK_CENTRE : "centre",
-                     FUELTANK_LEFT : "left",
-                     FUELTANK_RIGHT : "right",
-                     FUELTANK_LEFT_AUX : "left_aux",
-                     FUELTANK_RIGHT_AUX : "right_aux",
-                     FUELTANK_LEFT_TIP : "left_tip",
-                     FUELTANK_RIGHT_TIP : "right_tip",
-                     FUELTANK_EXTERNAL1 : "external1",
-                     FUELTANK_EXTERNAL2 : "external2",
-                     FUELTANK_CENTRE2 : "centre2" }
-
-def fuelTank2string(fuelTank):
-    """Get the string equivalent of the given fuelTank."""
-    return _fuelTankStrings[fuelTank] \
-           if fuelTank in _fuelTankStrings else None    
-
-#-------------------------------------------------------------------------------
-
-# 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))
-
-#-------------------------------------------------------------------------------
-
-languages = ["$system", "en_GB", "hu_HU"]
-
-#-------------------------------------------------------------------------------
-
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 134)
+++ src/mlx/flight.py	(revision 134)
@@ -0,0 +1,208 @@
+# Module related to the high-level tracking of the flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+import util
+
+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.flareTimeFromFS = False
+        self.entranceExam = False
+
+        self.options = Options()
+
+        self.aircraftType = None
+        self.aircraft = None
+        self.simulator = None
+
+        self.blockTimeStart = None
+        self.flightTimeStart = None
+        self.flightTimeEnd = None
+        self.blockTimeEnd = None
+
+        self._lastDistanceTime = None
+        self._previousLatitude = None
+        self._previousLongitude = None
+        self.flownDistance = 0.0
+
+        self.startFuel = None
+        self.endFuel = None
+
+        self._endCondition = threading.Condition()
+
+        self._flareStart = None
+        self._flareStartFS = None
+
+    @property
+    def config(self):
+        """Get the configuration."""
+        return self._gui.config
+
+    @property
+    def stage(self):
+        """Get the flight stage."""
+        return self._stage
+
+    @property
+    def bookedFlight(self):
+        """Get the booked flight."""
+        return self._gui.bookedFlight
+
+    @property
+    def zfw(self):
+        """Get the Zero-Fuel Weight of the flight."""
+        return self._gui.zfw
+
+    @property
+    def cruiseAltitude(self):
+        """Get the cruise altitude of the flight."""
+        return self._gui.cruiseAltitude
+
+    @property
+    def v1(self):
+        """Get the V1 speed of the flight."""
+        return self._gui.v1
+
+    @property
+    def vr(self):
+        """Get the Vr speed of the flight."""
+        return self._gui.vr
+
+    @property
+    def v2(self):
+        """Get the V2 speed of the flight."""
+        return self._gui.v2
+
+    @property
+    def vref(self):
+        """Get the VRef speed of the flight."""
+        return self._gui.vref
+
+    def handleState(self, oldState, currentState):
+        """Handle a new state information."""
+        self._updateFlownDistance(currentState)
+        
+        self.endFuel = sum(currentState.fuel)
+        if self.startFuel is None:
+            self.startFuel = self.endFuel
+
+    def setStage(self, timestamp, stage):
+        """Set the flight stage.
+
+        Returns if the stage has really changed."""
+        if stage!=self._stage:
+            self._stage = stage
+            self._gui.setStage(stage)
+            self.logger.stage(timestamp, stage)
+            if stage==const.STAGE_PUSHANDTAXI:
+                self.blockTimeStart = timestamp
+            elif stage==const.STAGE_TAKEOFF:
+                self.flightTimeStart = timestamp
+            elif stage==const.STAGE_TAXIAFTERLAND:
+                self.flightTimeEnd = timestamp
+            elif stage==const.STAGE_PARKING:
+                self.blockTimeEnd = timestamp
+            elif 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)
+
+    def getFleet(self, callback, force = False):
+        """Get the fleet and call the given callback."""
+        self._gui.getFleetAsync(callback = callback, force = force)
+
+    def _updateFlownDistance(self, currentState):
+        """Update the flown distance."""
+        if not currentState.onTheGround:
+            updateData = False
+            if self._lastDistanceTime is None or \
+               self._previousLatitude is None or \
+               self._previousLongitude is None:
+                updateData = True
+            elif currentState.timestamp >= (self._lastDistanceTime + 30.0):
+                updateData = True
+                self.flownDistance += self._getDistance(currentState)
+
+            if updateData:
+                self._previousLatitude = currentState.latitude
+                self._previousLongitude = currentState.longitude
+                self._lastDistanceTime = currentState.timestamp
+        else:
+            if self._lastDistanceTime is not None and \
+               self._previousLatitude is not None and \
+               self._previousLongitude is not None:
+                self.flownDistance += self._getDistance(currentState)
+                
+            self._lastDistanceTime = None
+
+    def _getDistance(self, currentState):
+        """Get the distance between the previous and the current state."""
+        return util.getDistCourse(self._previousLatitude, self._previousLongitude,
+                                  currentState.latitude, currentState.longitude)[0]
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 134)
+++ 	(revision )
@@ -1,208 +1,0 @@
-# Module related to the high-level tracking of the flight
-
-#---------------------------------------------------------------------------------------
-
-import const
-import util
-
-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.flareTimeFromFS = False
-        self.entranceExam = False
-
-        self.options = Options()
-
-        self.aircraftType = None
-        self.aircraft = None
-        self.simulator = None
-
-        self.blockTimeStart = None
-        self.flightTimeStart = None
-        self.flightTimeEnd = None
-        self.blockTimeEnd = None
-
-        self._lastDistanceTime = None
-        self._previousLatitude = None
-        self._previousLongitude = None
-        self.flownDistance = 0.0
-
-        self.startFuel = None
-        self.endFuel = None
-
-        self._endCondition = threading.Condition()
-
-        self._flareStart = None
-        self._flareStartFS = None
-
-    @property
-    def config(self):
-        """Get the configuration."""
-        return self._gui.config
-
-    @property
-    def stage(self):
-        """Get the flight stage."""
-        return self._stage
-
-    @property
-    def bookedFlight(self):
-        """Get the booked flight."""
-        return self._gui.bookedFlight
-
-    @property
-    def zfw(self):
-        """Get the Zero-Fuel Weight of the flight."""
-        return self._gui.zfw
-
-    @property
-    def cruiseAltitude(self):
-        """Get the cruise altitude of the flight."""
-        return self._gui.cruiseAltitude
-
-    @property
-    def v1(self):
-        """Get the V1 speed of the flight."""
-        return self._gui.v1
-
-    @property
-    def vr(self):
-        """Get the Vr speed of the flight."""
-        return self._gui.vr
-
-    @property
-    def v2(self):
-        """Get the V2 speed of the flight."""
-        return self._gui.v2
-
-    @property
-    def vref(self):
-        """Get the VRef speed of the flight."""
-        return self._gui.vref
-
-    def handleState(self, oldState, currentState):
-        """Handle a new state information."""
-        self._updateFlownDistance(currentState)
-        
-        self.endFuel = sum(currentState.fuel)
-        if self.startFuel is None:
-            self.startFuel = self.endFuel
-
-    def setStage(self, timestamp, stage):
-        """Set the flight stage.
-
-        Returns if the stage has really changed."""
-        if stage!=self._stage:
-            self._stage = stage
-            self._gui.setStage(stage)
-            self.logger.stage(timestamp, stage)
-            if stage==const.STAGE_PUSHANDTAXI:
-                self.blockTimeStart = timestamp
-            elif stage==const.STAGE_TAKEOFF:
-                self.flightTimeStart = timestamp
-            elif stage==const.STAGE_TAXIAFTERLAND:
-                self.flightTimeEnd = timestamp
-            elif stage==const.STAGE_PARKING:
-                self.blockTimeEnd = timestamp
-            elif 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)
-
-    def getFleet(self, callback, force = False):
-        """Get the fleet and call the given callback."""
-        self._gui.getFleetAsync(callback = callback, force = force)
-
-    def _updateFlownDistance(self, currentState):
-        """Update the flown distance."""
-        if not currentState.onTheGround:
-            updateData = False
-            if self._lastDistanceTime is None or \
-               self._previousLatitude is None or \
-               self._previousLongitude is None:
-                updateData = True
-            elif currentState.timestamp >= (self._lastDistanceTime + 30.0):
-                updateData = True
-                self.flownDistance += self._getDistance(currentState)
-
-            if updateData:
-                self._previousLatitude = currentState.latitude
-                self._previousLongitude = currentState.longitude
-                self._lastDistanceTime = currentState.timestamp
-        else:
-            if self._lastDistanceTime is not None and \
-               self._previousLatitude is not None and \
-               self._previousLongitude is not None:
-                self.flownDistance += self._getDistance(currentState)
-                
-            self._lastDistanceTime = None
-
-    def _getDistance(self, currentState):
-        """Get the distance between the previous and the current state."""
-        return util.getDistCourse(self._previousLatitude, self._previousLongitude,
-                                  currentState.latitude, currentState.longitude)[0]
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 152)
+++ src/mlx/fs.py	(revision 152)
@@ -0,0 +1,220 @@
+# Module for generic flight-simulator interfaces
+
+#-------------------------------------------------------------------------------
+
+import const
+from sound import startSound
+
+import fsuipc
+import threading
+import time
+
+#-------------------------------------------------------------------------------
+
+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 MessageThread(threading.Thread):
+    """Thread to handle messages."""
+    def __init__(self, config, simulator):
+        """Initialize the message thread with the given configuration and
+        simulator."""
+        super(MessageThread, self).__init__()
+
+        self._config = config
+        self._simulator = simulator
+
+        self._requestCondition = threading.Condition()
+        self._messages = []
+        self._nextMessageTime = None
+        self._toQuit = False
+
+        self.daemon = True
+
+    def add(self, messageType, text, duration, disconnect):
+        """Add the given message to the requested messages."""
+        with self._requestCondition:
+            self._messages.append((messageType, text, duration,
+                                   disconnect))
+            self._requestCondition.notify()
+
+    def quit(self):
+        """Quit the thread."""
+        with self._requestCondition:
+            self._toQuit = True
+            self._requestCondition.notifty()
+        self.join()
+
+    def run(self):
+        """Perform the thread's operation."""
+        while True:
+            (messageType, text, duration, disconnect) = (None, None, None, None)
+            with self._requestCondition:
+                now = time.time()
+                while not self._toQuit and \
+                      ((self._nextMessageTime is not None and \
+                        self._nextMessageTime>now) or \
+                       not self._messages):
+                    self._requestCondition.wait(1)
+                    now = time.time()
+
+                if self._toQuit: return
+                if self._nextMessageTime is None or \
+                   self._nextMessageTime<=now:
+                    self._nextMessageTime = None
+
+                    if self._messages:
+                        (messageType, text,
+                         duration, disconnect) = self._messages[0]
+                        del self._messages[0]
+
+            if text is not None:        
+                self._sendMessage(messageType, text, duration, disconnect)
+
+    def _sendMessage(self, messageType, text, duration, disconnect):
+        """Send the message and setup the next message time."""
+        messageLevel = self._config.getMessageTypeLevel(messageType)
+        if messageLevel==const.MESSAGELEVEL_SOUND or \
+           messageLevel==const.MESSAGELEVEL_BOTH:
+            startSound(const.SOUND_DING)
+        if (messageLevel==const.MESSAGELEVEL_FS or \
+            messageLevel==const.MESSAGELEVEL_BOTH):
+            if disconnect:
+                self._simulator.disconnect("[MLX] " + text,
+                                           duration = duration)
+            else:
+                self._simulator.sendMessage("[MLX] " + text,
+                                            duration = duration)
+        elif disconnecte:
+            self._simulator.disconnect()
+        self._nextMessageTime = time.time() + duration
+
+#-------------------------------------------------------------------------------
+
+_messageThread = None
+
+#-------------------------------------------------------------------------------
+
+def setupMessageSending(config, simulator):
+    """Setup message sending with the given config and simulator."""
+    global _messageThread
+    if _messageThread is not None:
+        _messageThread.quit()
+    _messageThread = MessageThread(config, simulator)
+    _messageThread.start()
+
+#-------------------------------------------------------------------------------
+
+def sendMessage(messageType, text, duration = 3, disconnect = False):
+    """Send the given message of the given type into the simulator and/or play
+    a corresponding sound."""
+    global _messageThread
+    if _messageThread is not None:
+        _messageThread.add(messageType, text, duration, disconnect)
+
+#-------------------------------------------------------------------------------
+
+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)
+    - latitude (in degrees, North is positive)
+    - longitude (in degrees, East is positive)
+    - 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)
+    - visibility: the visibility in metres (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 152)
+++ 	(revision )
@@ -1,220 +1,0 @@
-# Module for generic flight-simulator interfaces
-
-#-------------------------------------------------------------------------------
-
-import const
-from sound import startSound
-
-import fsuipc
-import threading
-import time
-
-#-------------------------------------------------------------------------------
-
-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 MessageThread(threading.Thread):
-    """Thread to handle messages."""
-    def __init__(self, config, simulator):
-        """Initialize the message thread with the given configuration and
-        simulator."""
-        super(MessageThread, self).__init__()
-
-        self._config = config
-        self._simulator = simulator
-
-        self._requestCondition = threading.Condition()
-        self._messages = []
-        self._nextMessageTime = None
-        self._toQuit = False
-
-        self.daemon = True
-
-    def add(self, messageType, text, duration, disconnect):
-        """Add the given message to the requested messages."""
-        with self._requestCondition:
-            self._messages.append((messageType, text, duration,
-                                   disconnect))
-            self._requestCondition.notify()
-
-    def quit(self):
-        """Quit the thread."""
-        with self._requestCondition:
-            self._toQuit = True
-            self._requestCondition.notifty()
-        self.join()
-
-    def run(self):
-        """Perform the thread's operation."""
-        while True:
-            (messageType, text, duration, disconnect) = (None, None, None, None)
-            with self._requestCondition:
-                now = time.time()
-                while not self._toQuit and \
-                      ((self._nextMessageTime is not None and \
-                        self._nextMessageTime>now) or \
-                       not self._messages):
-                    self._requestCondition.wait(1)
-                    now = time.time()
-
-                if self._toQuit: return
-                if self._nextMessageTime is None or \
-                   self._nextMessageTime<=now:
-                    self._nextMessageTime = None
-
-                    if self._messages:
-                        (messageType, text,
-                         duration, disconnect) = self._messages[0]
-                        del self._messages[0]
-
-            if text is not None:        
-                self._sendMessage(messageType, text, duration, disconnect)
-
-    def _sendMessage(self, messageType, text, duration, disconnect):
-        """Send the message and setup the next message time."""
-        messageLevel = self._config.getMessageTypeLevel(messageType)
-        if messageLevel==const.MESSAGELEVEL_SOUND or \
-           messageLevel==const.MESSAGELEVEL_BOTH:
-            startSound(const.SOUND_DING)
-        if (messageLevel==const.MESSAGELEVEL_FS or \
-            messageLevel==const.MESSAGELEVEL_BOTH):
-            if disconnect:
-                self._simulator.disconnect("[MLX] " + text,
-                                           duration = duration)
-            else:
-                self._simulator.sendMessage("[MLX] " + text,
-                                            duration = duration)
-        elif disconnecte:
-            self._simulator.disconnect()
-        self._nextMessageTime = time.time() + duration
-
-#-------------------------------------------------------------------------------
-
-_messageThread = None
-
-#-------------------------------------------------------------------------------
-
-def setupMessageSending(config, simulator):
-    """Setup message sending with the given config and simulator."""
-    global _messageThread
-    if _messageThread is not None:
-        _messageThread.quit()
-    _messageThread = MessageThread(config, simulator)
-    _messageThread.start()
-
-#-------------------------------------------------------------------------------
-
-def sendMessage(messageType, text, duration = 3, disconnect = False):
-    """Send the given message of the given type into the simulator and/or play
-    a corresponding sound."""
-    global _messageThread
-    if _messageThread is not None:
-        _messageThread.add(messageType, text, duration, disconnect)
-
-#-------------------------------------------------------------------------------
-
-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)
-    - latitude (in degrees, North is positive)
-    - longitude (in degrees, East is positive)
-    - 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)
-    - visibility: the visibility in metres (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 162)
+++ src/mlx/fsuipc.py	(revision 162)
@@ -0,0 +1,1476 @@
+# Module handling the connection to FSUIPC
+
+#------------------------------------------------------------------------------
+
+import fs
+import const
+import util
+import acft
+
+import threading
+import os
+import time
+import calendar
+import sys
+import codecs
+
+if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
+    import pyuipc
+else:
+    import pyuipc_sim as pyuipc
+
+#------------------------------------------------------------------------------
+
+# The mapping of tank types to FSUIPC offsets
+_tank2offset = { const.FUELTANK_CENTRE : 0x0b74,
+                 const.FUELTANK_LEFT : 0x0b7c,
+                 const.FUELTANK_RIGHT : 0x0b94,
+                 const.FUELTANK_LEFT_AUX : 0x0b84,
+                 const.FUELTANK_RIGHT_AUX : 0x0b9c,
+                 const.FUELTANK_LEFT_TIP : 0x0b8c,
+                 const.FUELTANK_RIGHT_TIP : 0x0ba4,
+                 const.FUELTANK_EXTERNAL1 : 0x1254,
+                 const.FUELTANK_EXTERNAL2 : 0x125c,
+                 const.FUELTANK_CENTRE2 : 0x1244 }
+
+#------------------------------------------------------------------------------
+
+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:
+            request = Handler.Request(True, data, callback, extra)
+            #print "fsuipc.Handler.requestWrite", request
+            self._requests.append(request)
+            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 self._requests or \
+               (timeout is not None and timeout <= 0.0):
+                return
+            
+            self._requestCondition.wait(timeout)
+                
+    def _disconnect(self):
+        """Disconnect from the flight simulator."""
+        print "fsuipc.Handler._disconnect"
+        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()
+
+        #print "fsuipc.Handler._processRequest", request
+
+        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
+                   (0x1274, "h") ]           # Text display mode
+
+    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
+
+    TIME_SYNC_INTERVAL = 3.0
+
+    @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._scroll = False
+
+        self._syncTime = False
+        self._nextSyncTime = -1
+        
+        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._nextSyncTime = -1
+            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 requestWeights(self, callback):
+        """Request the following weights: DOW, ZFW, payload.
+
+        These values will be passed to the callback function in this order, as
+        separate arguments."""        
+        self._handler.requestRead([(0x13fc, "d")], self._handlePayloadCount,
+                                  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 sendMessage(self, message, duration = 3,
+                    _disconnect = False):
+        """Send a message to the pilot via the simulator.
+
+        duration is the number of seconds to keep the message displayed."""
+        
+        if self._scroll:
+            if duration==0: duration = -1
+            elif duration == 1: duration = -2
+            else: duration = -duration
+
+        data = [(0x3380, -1 - len(message), message),
+                (0x32fa, 'h', duration)]
+
+        #if _disconnect:
+        #    print "fsuipc.Simulator.sendMessage(disconnect)", message
+
+        self._handler.requestWrite(data, self._handleMessageSent,
+                                   extra = _disconnect)
+
+    def getFuel(self, tanks, callback):
+        """Get the fuel from the given tanks.
+
+        The callback will be called with a list of two-tuples, where the tuples
+        have the following items:
+        - the current weight of the fuel in the tank (in kgs)
+        - the current total capacity of the tank (in kgs)."""
+        data = [(0x0af4, "H")]     # Fuel weight
+        for tank in tanks:
+            offset = _tank2offset[tank]
+            data.append( (offset, "u") )     # tank level
+            data.append( (offset+4, "u") )   # tank capacity
+
+        self._handler.requestRead(data, self._handleFuelRetrieved,
+                                  extra = callback)
+
+    def setFuelLevel(self, levels):
+        """Set the fuel level to the given ones.
+
+        levels is an array of two-tuples, where each tuple consists of the
+        following:
+        - the const.FUELTANK_XXX constant denoting the tank that must be set,
+        - the requested level of the fuel as a floating-point value between 0.0
+        and 1.0."""
+        data = []
+        for (tank, level) in levels:
+            offset = _tank2offset[tank]
+            data.append( (offset, "u", long(level * 128.8 * 65536.0)) )
+        self._handler.requestWrite(data, self._handleFuelWritten)
+
+    def enableTimeSync(self):
+        """Enable the time synchronization."""
+        self._nextSyncTime = -1
+        self._syncTime = True
+            
+    def disableTimeSync(self):
+        """Enable the time synchronization."""
+        self._syncTime = False
+        self._nextSyncTime = -1
+            
+    def disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested 
+
+        print "fsuipc.Simulator.disconnect", closingMessage, duration
+        
+        self._stopNormal()
+        if closingMessage is None:
+            self._handler.disconnect()
+        else:
+            self.sendMessage(closingMessage, duration = duration,
+                             _disconnect = True)
+
+    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])
+
+        self._scroll = data[7]!=0
+
+        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._checkTimeSync(aircraftState)
+        
+            self._aircraft.handleState(aircraftState)
+
+    def _checkTimeSync(self, aircraftState):
+        """Check if we need to synchronize the FS time."""
+        if not self._syncTime or aircraftState.paused or \
+           self._flareRequestID is not None:
+            self._nextSyncTime = -1
+            return
+
+        now = time.time()
+        seconds = time.gmtime(now).tm_sec
+
+        if seconds>30 and seconds<59:
+            if self._nextSyncTime > (now - 0.49):
+                return
+            
+            self._handler.requestWrite([(0x023a, "b", int(seconds))],
+                                       self._handleTimeSynced)
+            
+            #print "Set the seconds to ", seconds
+
+            if self._nextSyncTime<0:
+                self._nextSyncTime = now
+                
+            self._nextSyncTime += Simulator.TIME_SYNC_INTERVAL
+        else:
+            self._nextSyncTime = -1
+
+    def _handleTimeSynced(self, success, extra):
+        """Callback for the time sync result."""
+        pass
+        
+    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)[0],
+                                    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))
+
+    def _handlePayloadCount(self, data, callback):
+        """Callback for the payload count retrieval request."""
+        payloadCount = data[0]
+        data = [(0x3bfc, "d"), (0x30c0, "f")]
+        for i in range(0, payloadCount):
+            data.append((0x1400 + i*48, "f"))
+        
+        self._handler.requestRead(data, self._handleWeights,
+                                  extra = callback)
+
+    def _handleWeights(self, data, callback):
+        """Callback for the weights retrieval request."""
+        zfw = data[0] * const.LBSTOKG / 256.0
+        grossWeight = data[1] * const.LBSTOKG
+        payload = sum(data[2:]) * const.LBSTOKG
+        dow = zfw - payload
+        callback(dow, payload, zfw, grossWeight)
+
+    def _handleMessageSent(self, success, disconnect):
+        """Callback for a message sending request."""
+        #print "fsuipc.Simulator._handleMessageSent", disconnect
+        if disconnect:
+            self._handler.disconnect()
+
+    def _handleFuelRetrieved(self, data, callback):
+        """Callback for a fuel retrieval request."""
+        fuelWeight = data[0] / 256.0
+        result = []
+        for i in range(1, len(data), 2):
+            capacity = data[i+1] * fuelWeight * const.LBSTOKG
+            amount = data[i] * capacity / 128.0 / 65536.0
+            result.append( (amount, capacity) )
+
+        callback(result)
+                                                  
+    def _handleFuelWritten(self, success, extra):
+        """Callback for a fuel setting request."""
+        pass
+                                                  
+#------------------------------------------------------------------------------
+
+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"),
+                      ("latitude", 0x0560, "l"),
+                      ("longitude", 0x0568, "l"),
+                      ("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"),
+                      ("visibility", 0x0e8a, "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.latitude = data[self._monidx_latitude] * \
+                         90.0 / 10001750.0 / 65536.0 / 65536.0
+
+        state.longitude = data[self._monidx_longitude] * \
+                          360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
+        if state.longitude>180.0: state.longitude = 360.0 - state.longitude
+        
+        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
+
+        state.visibility = data[self._monidx_visibility]*1609.344/100.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, fuelTanks, 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.
+
+        fuelTanks is an array of const.FUELTANK_XXX constants about the
+        aircraft's fuel tanks. They will be converted to offsets.
+
+        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._fuelTanks = fuelTanks
+        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 tank in self._fuelTanks:
+            offset = _tank2offset[tank]
+            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._fuelTanks), 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],
+                     fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
+                     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],
+                     fuelTanks = acft.Boeing737.fuelTanks,
+                     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],
+                     fuelTanks = acft.Boeing767.fuelTanks,
+                     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],
+                     fuelTanks = acft.DH8D.fuelTanks,
+                     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],
+                     fuelTanks = acft.CRJ2.fuelTanks,
+                     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],
+                     fuelTanks = acft.F70.fuelTanks,
+                     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],
+                     fuelTanks = acft.DC3.fuelTanks,
+                     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],
+                     fuelTanks = acft.T134.fuelTanks,
+                     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],
+                     fuelTanks = acft.T154.fuelTanks,
+                     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],
+                     fuelTanks = acft.YK40.fuelTanks,
+                     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 162)
+++ 	(revision )
@@ -1,1476 +1,0 @@
-# Module handling the connection to FSUIPC
-
-#------------------------------------------------------------------------------
-
-import fs
-import const
-import util
-import acft
-
-import threading
-import os
-import time
-import calendar
-import sys
-import codecs
-
-if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
-    import pyuipc
-else:
-    import pyuipc_sim as pyuipc
-
-#------------------------------------------------------------------------------
-
-# The mapping of tank types to FSUIPC offsets
-_tank2offset = { const.FUELTANK_CENTRE : 0x0b74,
-                 const.FUELTANK_LEFT : 0x0b7c,
-                 const.FUELTANK_RIGHT : 0x0b94,
-                 const.FUELTANK_LEFT_AUX : 0x0b84,
-                 const.FUELTANK_RIGHT_AUX : 0x0b9c,
-                 const.FUELTANK_LEFT_TIP : 0x0b8c,
-                 const.FUELTANK_RIGHT_TIP : 0x0ba4,
-                 const.FUELTANK_EXTERNAL1 : 0x1254,
-                 const.FUELTANK_EXTERNAL2 : 0x125c,
-                 const.FUELTANK_CENTRE2 : 0x1244 }
-
-#------------------------------------------------------------------------------
-
-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:
-            request = Handler.Request(True, data, callback, extra)
-            #print "fsuipc.Handler.requestWrite", request
-            self._requests.append(request)
-            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 self._requests or \
-               (timeout is not None and timeout <= 0.0):
-                return
-            
-            self._requestCondition.wait(timeout)
-                
-    def _disconnect(self):
-        """Disconnect from the flight simulator."""
-        print "fsuipc.Handler._disconnect"
-        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()
-
-        #print "fsuipc.Handler._processRequest", request
-
-        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
-                   (0x1274, "h") ]           # Text display mode
-
-    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
-
-    TIME_SYNC_INTERVAL = 3.0
-
-    @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._scroll = False
-
-        self._syncTime = False
-        self._nextSyncTime = -1
-        
-        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._nextSyncTime = -1
-            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 requestWeights(self, callback):
-        """Request the following weights: DOW, ZFW, payload.
-
-        These values will be passed to the callback function in this order, as
-        separate arguments."""        
-        self._handler.requestRead([(0x13fc, "d")], self._handlePayloadCount,
-                                  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 sendMessage(self, message, duration = 3,
-                    _disconnect = False):
-        """Send a message to the pilot via the simulator.
-
-        duration is the number of seconds to keep the message displayed."""
-        
-        if self._scroll:
-            if duration==0: duration = -1
-            elif duration == 1: duration = -2
-            else: duration = -duration
-
-        data = [(0x3380, -1 - len(message), message),
-                (0x32fa, 'h', duration)]
-
-        #if _disconnect:
-        #    print "fsuipc.Simulator.sendMessage(disconnect)", message
-
-        self._handler.requestWrite(data, self._handleMessageSent,
-                                   extra = _disconnect)
-
-    def getFuel(self, tanks, callback):
-        """Get the fuel from the given tanks.
-
-        The callback will be called with a list of two-tuples, where the tuples
-        have the following items:
-        - the current weight of the fuel in the tank (in kgs)
-        - the current total capacity of the tank (in kgs)."""
-        data = [(0x0af4, "H")]     # Fuel weight
-        for tank in tanks:
-            offset = _tank2offset[tank]
-            data.append( (offset, "u") )     # tank level
-            data.append( (offset+4, "u") )   # tank capacity
-
-        self._handler.requestRead(data, self._handleFuelRetrieved,
-                                  extra = callback)
-
-    def setFuelLevel(self, levels):
-        """Set the fuel level to the given ones.
-
-        levels is an array of two-tuples, where each tuple consists of the
-        following:
-        - the const.FUELTANK_XXX constant denoting the tank that must be set,
-        - the requested level of the fuel as a floating-point value between 0.0
-        and 1.0."""
-        data = []
-        for (tank, level) in levels:
-            offset = _tank2offset[tank]
-            data.append( (offset, "u", long(level * 128.8 * 65536.0)) )
-        self._handler.requestWrite(data, self._handleFuelWritten)
-
-    def enableTimeSync(self):
-        """Enable the time synchronization."""
-        self._nextSyncTime = -1
-        self._syncTime = True
-            
-    def disableTimeSync(self):
-        """Enable the time synchronization."""
-        self._syncTime = False
-        self._nextSyncTime = -1
-            
-    def disconnect(self, closingMessage = None, duration = 3):
-        """Disconnect from the simulator."""
-        assert not self._monitoringRequested 
-
-        print "fsuipc.Simulator.disconnect", closingMessage, duration
-        
-        self._stopNormal()
-        if closingMessage is None:
-            self._handler.disconnect()
-        else:
-            self.sendMessage(closingMessage, duration = duration,
-                             _disconnect = True)
-
-    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])
-
-        self._scroll = data[7]!=0
-
-        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._checkTimeSync(aircraftState)
-        
-            self._aircraft.handleState(aircraftState)
-
-    def _checkTimeSync(self, aircraftState):
-        """Check if we need to synchronize the FS time."""
-        if not self._syncTime or aircraftState.paused or \
-           self._flareRequestID is not None:
-            self._nextSyncTime = -1
-            return
-
-        now = time.time()
-        seconds = time.gmtime(now).tm_sec
-
-        if seconds>30 and seconds<59:
-            if self._nextSyncTime > (now - 0.49):
-                return
-            
-            self._handler.requestWrite([(0x023a, "b", int(seconds))],
-                                       self._handleTimeSynced)
-            
-            #print "Set the seconds to ", seconds
-
-            if self._nextSyncTime<0:
-                self._nextSyncTime = now
-                
-            self._nextSyncTime += Simulator.TIME_SYNC_INTERVAL
-        else:
-            self._nextSyncTime = -1
-
-    def _handleTimeSynced(self, success, extra):
-        """Callback for the time sync result."""
-        pass
-        
-    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)[0],
-                                    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))
-
-    def _handlePayloadCount(self, data, callback):
-        """Callback for the payload count retrieval request."""
-        payloadCount = data[0]
-        data = [(0x3bfc, "d"), (0x30c0, "f")]
-        for i in range(0, payloadCount):
-            data.append((0x1400 + i*48, "f"))
-        
-        self._handler.requestRead(data, self._handleWeights,
-                                  extra = callback)
-
-    def _handleWeights(self, data, callback):
-        """Callback for the weights retrieval request."""
-        zfw = data[0] * const.LBSTOKG / 256.0
-        grossWeight = data[1] * const.LBSTOKG
-        payload = sum(data[2:]) * const.LBSTOKG
-        dow = zfw - payload
-        callback(dow, payload, zfw, grossWeight)
-
-    def _handleMessageSent(self, success, disconnect):
-        """Callback for a message sending request."""
-        #print "fsuipc.Simulator._handleMessageSent", disconnect
-        if disconnect:
-            self._handler.disconnect()
-
-    def _handleFuelRetrieved(self, data, callback):
-        """Callback for a fuel retrieval request."""
-        fuelWeight = data[0] / 256.0
-        result = []
-        for i in range(1, len(data), 2):
-            capacity = data[i+1] * fuelWeight * const.LBSTOKG
-            amount = data[i] * capacity / 128.0 / 65536.0
-            result.append( (amount, capacity) )
-
-        callback(result)
-                                                  
-    def _handleFuelWritten(self, success, extra):
-        """Callback for a fuel setting request."""
-        pass
-                                                  
-#------------------------------------------------------------------------------
-
-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"),
-                      ("latitude", 0x0560, "l"),
-                      ("longitude", 0x0568, "l"),
-                      ("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"),
-                      ("visibility", 0x0e8a, "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.latitude = data[self._monidx_latitude] * \
-                         90.0 / 10001750.0 / 65536.0 / 65536.0
-
-        state.longitude = data[self._monidx_longitude] * \
-                          360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.0
-        if state.longitude>180.0: state.longitude = 360.0 - state.longitude
-        
-        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
-
-        state.visibility = data[self._monidx_visibility]*1609.344/100.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, fuelTanks, 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.
-
-        fuelTanks is an array of const.FUELTANK_XXX constants about the
-        aircraft's fuel tanks. They will be converted to offsets.
-
-        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._fuelTanks = fuelTanks
-        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 tank in self._fuelTanks:
-            offset = _tank2offset[tank]
-            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._fuelTanks), 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],
-                     fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT],
-                     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],
-                     fuelTanks = acft.Boeing737.fuelTanks,
-                     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],
-                     fuelTanks = acft.Boeing767.fuelTanks,
-                     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],
-                     fuelTanks = acft.DH8D.fuelTanks,
-                     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],
-                     fuelTanks = acft.CRJ2.fuelTanks,
-                     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],
-                     fuelTanks = acft.F70.fuelTanks,
-                     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],
-                     fuelTanks = acft.DC3.fuelTanks,
-                     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],
-                     fuelTanks = acft.T134.fuelTanks,
-                     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],
-                     fuelTanks = acft.T154.fuelTanks,
-                     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],
-                     fuelTanks = acft.YK40.fuelTanks,
-                     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 151)
+++ src/mlx/gui/common.py	(revision 151)
@@ -0,0 +1,193 @@
+# Common things for the GUI
+
+import mlx.const as _const
+
+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
+    MESSAGETYPE_INFO = gtk.MESSAGE_INFO
+    RESPONSETYPE_OK = gtk.RESPONSE_OK
+    RESPONSETYPE_YES = gtk.RESPONSE_YES
+    RESPONSETYPE_NO = gtk.RESPONSE_NO
+    RESPONSETYPE_ACCEPT = gtk.RESPONSE_ACCEPT
+    RESPONSETYPE_REJECT = gtk.RESPONSE_REJECT
+    RESPONSETYPE_CANCEL = gtk.RESPONSE_CANCEL
+    ACCEL_VISIBLE = gtk.ACCEL_VISIBLE
+    CONTROL_MASK = gdk.CONTROL_MASK
+    DIALOG_MODAL = gtk.DIALOG_MODAL
+    WRAP_WORD = gtk.WRAP_WORD
+    JUSTIFY_CENTER = gtk.JUSTIFY_CENTER
+
+    CONTROL_MASK = gdk.CONTROL_MASK
+    SHIFT_MASK = gdk.SHIFT_MASK
+    BUTTON1_MASK = gdk.BUTTON1_MASK
+
+    SCROLL_UP = gdk.SCROLL_UP
+    SCROLL_DOWN = gdk.SCROLL_DOWN
+
+    SPIN_USER_DEFINED = gtk.SPIN_USER_DEFINED
+
+    FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
+    FILE_CHOOSER_ACTION_OPEN = gtk.FILE_CHOOSER_ACTION_OPEN
+    FILE_CHOOSER_ACTION_SAVE = gtk.FILE_CHOOSER_ACTION_SAVE
+
+    def text2unicode(text):
+        """Convert the given text, returned by a Gtk widget, to Unicode."""
+        return unicode(text)
+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
+    MESSAGETYPE_INFO = gtk.MessageType.INFO
+    RESPONSETYPE_OK = gtk.ResponseType.OK
+    RESPONSETYPE_YES = gtk.ResponseType.YES
+    RESPONSETYPE_NO = gtk.ResponseType.NO
+    RESPONSETYPE_ACCEPT = gtk.ResponseType.ACCEPT
+    RESPONSETYPE_REJECT = gtk.ResponseType.REJECT
+    RESPONSETYPE_CANCEL = gtk.ResponseType.CANCEL
+    ACCEL_VISIBLE = gtk.AccelFlags.VISIBLE
+    CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
+    DIALOG_MODAL = gtk.DialogFlags.MODAL
+    WRAP_WORD = gtk.WrapMode.WORD
+    JUSTIFY_CENTER = gtk.Justification.CENTER
+
+    CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
+    SHIFT_MASK = gdk.ModifierType.SHIFT_MASK
+    BUTTON1_MASK = gdk.ModifierType.BUTTON1_MASK
+
+    SCROLL_UP = gdk.ScrollDirection.UP
+    SCROLL_DOWN = gdk.ScrollDirection.DOWN
+
+    SPIN_USER_DEFINED = gtk.SpinType.USER_DEFINED
+
+    FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FileChooserAction.SELECT_FOLDER
+    FILE_CHOOSER_ACTION_OPEN = gtk.FileChooserAction.OPEN
+    FILE_CHOOSER_ACTION_SAVE = gtk.FileChooserAction.SAVE
+
+    import codecs
+    _utf8Decoder = codecs.getdecoder("utf-8")
+    
+    def text2unicode(str):
+        """Convert the given text, returned by a Gtk widget, to Unicode."""
+        return _utf8Decoder(str)[0]
+
+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()
+
+#------------------------------------------------------------------------------
+
+class IntegerEntry(gtk.Entry):
+    """An entry that allows only either an empty value, or an integer."""
+    def __init__(self, defaultValue = None):
+        """Construct the entry."""
+        gtk.Entry.__init__(self)
+
+        self.set_alignment(1.0)
+
+        self._defaultValue = defaultValue
+        self._currentInteger = defaultValue
+        self._selfSetting = False
+        self._set_text()
+
+        self.connect("changed", self._handle_changed)
+
+    def get_int(self):
+        """Get the integer."""
+        return self._currentInteger
+
+    def set_int(self, value):
+        """Set the integer."""
+        if value!=self._currentInteger:
+            self._currentInteger = value
+            self.emit("integer-changed", self._currentInteger)
+        self._set_text()
+    
+    def _handle_changed(self, widget):
+        """Handle the changed signal."""
+        if self._selfSetting:
+            return
+        text = self.get_text()
+        if text=="":
+            self.set_int(self._defaultValue)
+        else:
+            try:
+                self.set_int(int(text))
+            except:
+                self._set_text()
+
+    def _set_text(self):
+        """Set the text value from the current integer."""
+        self._selfSetting = True
+        self.set_text("" if self._currentInteger is None
+                      else str(self._currentInteger))
+        self._selfSetting = False
+                
+#------------------------------------------------------------------------------
+
+gobject.signal_new("integer-changed", IntegerEntry, gobject.SIGNAL_RUN_FIRST,
+                   None, (object,))
+
+#------------------------------------------------------------------------------
+
+WINDOW_TITLE_BASE = "MAVA Logger X " + _const.VERSION
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 151)
+++ 	(revision )
@@ -1,193 +1,0 @@
-# Common things for the GUI
-
-import mlx.const as _const
-
-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
-    MESSAGETYPE_INFO = gtk.MESSAGE_INFO
-    RESPONSETYPE_OK = gtk.RESPONSE_OK
-    RESPONSETYPE_YES = gtk.RESPONSE_YES
-    RESPONSETYPE_NO = gtk.RESPONSE_NO
-    RESPONSETYPE_ACCEPT = gtk.RESPONSE_ACCEPT
-    RESPONSETYPE_REJECT = gtk.RESPONSE_REJECT
-    RESPONSETYPE_CANCEL = gtk.RESPONSE_CANCEL
-    ACCEL_VISIBLE = gtk.ACCEL_VISIBLE
-    CONTROL_MASK = gdk.CONTROL_MASK
-    DIALOG_MODAL = gtk.DIALOG_MODAL
-    WRAP_WORD = gtk.WRAP_WORD
-    JUSTIFY_CENTER = gtk.JUSTIFY_CENTER
-
-    CONTROL_MASK = gdk.CONTROL_MASK
-    SHIFT_MASK = gdk.SHIFT_MASK
-    BUTTON1_MASK = gdk.BUTTON1_MASK
-
-    SCROLL_UP = gdk.SCROLL_UP
-    SCROLL_DOWN = gdk.SCROLL_DOWN
-
-    SPIN_USER_DEFINED = gtk.SPIN_USER_DEFINED
-
-    FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
-    FILE_CHOOSER_ACTION_OPEN = gtk.FILE_CHOOSER_ACTION_OPEN
-    FILE_CHOOSER_ACTION_SAVE = gtk.FILE_CHOOSER_ACTION_SAVE
-
-    def text2unicode(text):
-        """Convert the given text, returned by a Gtk widget, to Unicode."""
-        return unicode(text)
-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
-    MESSAGETYPE_INFO = gtk.MessageType.INFO
-    RESPONSETYPE_OK = gtk.ResponseType.OK
-    RESPONSETYPE_YES = gtk.ResponseType.YES
-    RESPONSETYPE_NO = gtk.ResponseType.NO
-    RESPONSETYPE_ACCEPT = gtk.ResponseType.ACCEPT
-    RESPONSETYPE_REJECT = gtk.ResponseType.REJECT
-    RESPONSETYPE_CANCEL = gtk.ResponseType.CANCEL
-    ACCEL_VISIBLE = gtk.AccelFlags.VISIBLE
-    CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
-    DIALOG_MODAL = gtk.DialogFlags.MODAL
-    WRAP_WORD = gtk.WrapMode.WORD
-    JUSTIFY_CENTER = gtk.Justification.CENTER
-
-    CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
-    SHIFT_MASK = gdk.ModifierType.SHIFT_MASK
-    BUTTON1_MASK = gdk.ModifierType.BUTTON1_MASK
-
-    SCROLL_UP = gdk.ScrollDirection.UP
-    SCROLL_DOWN = gdk.ScrollDirection.DOWN
-
-    SPIN_USER_DEFINED = gtk.SpinType.USER_DEFINED
-
-    FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FileChooserAction.SELECT_FOLDER
-    FILE_CHOOSER_ACTION_OPEN = gtk.FileChooserAction.OPEN
-    FILE_CHOOSER_ACTION_SAVE = gtk.FileChooserAction.SAVE
-
-    import codecs
-    _utf8Decoder = codecs.getdecoder("utf-8")
-    
-    def text2unicode(str):
-        """Convert the given text, returned by a Gtk widget, to Unicode."""
-        return _utf8Decoder(str)[0]
-
-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()
-
-#------------------------------------------------------------------------------
-
-class IntegerEntry(gtk.Entry):
-    """An entry that allows only either an empty value, or an integer."""
-    def __init__(self, defaultValue = None):
-        """Construct the entry."""
-        gtk.Entry.__init__(self)
-
-        self.set_alignment(1.0)
-
-        self._defaultValue = defaultValue
-        self._currentInteger = defaultValue
-        self._selfSetting = False
-        self._set_text()
-
-        self.connect("changed", self._handle_changed)
-
-    def get_int(self):
-        """Get the integer."""
-        return self._currentInteger
-
-    def set_int(self, value):
-        """Set the integer."""
-        if value!=self._currentInteger:
-            self._currentInteger = value
-            self.emit("integer-changed", self._currentInteger)
-        self._set_text()
-    
-    def _handle_changed(self, widget):
-        """Handle the changed signal."""
-        if self._selfSetting:
-            return
-        text = self.get_text()
-        if text=="":
-            self.set_int(self._defaultValue)
-        else:
-            try:
-                self.set_int(int(text))
-            except:
-                self._set_text()
-
-    def _set_text(self):
-        """Set the text value from the current integer."""
-        self._selfSetting = True
-        self.set_text("" if self._currentInteger is None
-                      else str(self._currentInteger))
-        self._selfSetting = False
-                
-#------------------------------------------------------------------------------
-
-gobject.signal_new("integer-changed", IntegerEntry, gobject.SIGNAL_RUN_FIRST,
-                   None, (object,))
-
-#------------------------------------------------------------------------------
-
-WINDOW_TITLE_BASE = "MAVA Logger X " + _const.VERSION
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 164)
+++ src/mlx/gui/flight.py	(revision 164)
@@ -0,0 +1,2646 @@
+# The flight handling "wizard"
+
+from mlx.gui.common import *
+
+import mlx.const as const
+import mlx.fs as fs
+import mlx.acft as acft
+from mlx.checks import PayloadChecker
+import mlx.util as util
+from mlx.pirep import PIREP
+from mlx.i18n import xstr
+
+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, completedHelp = None):
+        """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)
+
+        mainBox = gtk.VBox()        
+
+        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(mainBox)
+        self._vbox.pack_start(alignment, True, True, 0)
+        
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.set_padding(padding_top = 0, padding_bottom = 16,
+                              padding_left = 0, padding_right = 0)
+
+        self._help = help
+        self._completedHelp = completedHelp
+
+        if self._completedHelp is None or \
+           len(help.splitlines())>=len(completedHelp.splitlines()):
+            longerHelp = help
+        else:
+            longerHelp = completedHelp
+        
+        self._helpLabel = gtk.Label(completedHelp)
+        # FIXME: should be a constant in common
+        self._helpLabel.set_justify(gtk.Justification.CENTER if pygobject
+                                    else gtk.JUSTIFY_CENTER)
+        self._helpLabel.set_use_markup(True)
+        alignment.add(self._helpLabel)
+        mainBox.pack_start(alignment, False, False, 0)
+
+        self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                            xscale = 1.0, yscale = 1.0)
+        mainBox.pack_start(self._mainAlignment, True, True, 0)
+                                            
+        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._completed = 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, sensitive = True,
+                  tooltip = None, clicked = None):
+        """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
+        button.set_sensitive(sensitive)
+        if tooltip is not None:
+            button.set_tooltip_text(tooltip)
+        if clicked is not None:
+            button.connect("clicked", clicked)
+        return button
+
+    def addPreviousButton(self, sensitive = True, clicked = None):
+        """Add the 'Next' button to the page."""
+        return self.addButton(xstr("button_previous"),
+                              sensitive = sensitive,
+                              tooltip = xstr("button_previous_tooltip"),
+                              clicked = clicked)
+
+    def addNextButton(self, default = True, sensitive = True,
+                      clicked = None):
+        """Add the 'Next' button to the page."""
+        return self.addButton(xstr("button_next"),
+                              default = default,
+                              sensitive = sensitive,
+                              tooltip = xstr("button_next_tooltip"),
+                              clicked = clicked)
+
+    def initialize(self):
+        """Initialize the page.
+
+        It sets up the primary help, and calls the activate() function."""
+        self._helpLabel.set_markup(self._help)
+        self._helpLabel.set_sensitive(True)
+        self.activate()
+
+    def activate(self):
+        """Called when this page becomes active.
+
+        This default implementation does nothing."""
+        pass
+
+    def complete(self):
+        """Called when the page is completed.
+
+        It greys out/changes the help text and then calls finalize()."""
+        self.finalize()
+        if self._completedHelp is None:
+            self._helpLabel.set_sensitive(False)
+        else:
+            self._helpLabel.set_markup(self._completedHelp)
+        self._completed = True
+
+    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._completed = 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."""
+        super(LoginPage, self).__init__(wizard, xstr("login"),
+                                        xstr("loginHelp"))
+
+        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, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("label_pilotID"))
+        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(xstr("login_pilotID_tooltip"))
+        table.attach(self._pilotID, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._pilotID)
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(xstr("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(xstr("login_password_tooltip"))
+        table.attach(self._password, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._password)
+
+        self._rememberButton = gtk.CheckButton(xstr("remember_password"))
+        self._rememberButton.set_use_underline(True)
+        self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
+        table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
+
+        self._loginButton = self.addButton(xstr("button_login"), default = True)
+        self._loginButton.set_sensitive(False)
+        self._loginButton.connect("clicked", self._loginClicked)
+        self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))        
+
+    def activate(self):
+        """Activate the page."""
+        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(xstr("login_busy"))
+        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(parent = self._wizard.gui.mainWindow,
+                                           type = MESSAGETYPE_ERROR,
+                                           message_format = xstr("login_invalid"))
+                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+                dialog.set_title(WINDOW_TITLE_BASE)
+                dialog.format_secondary_markup(xstr("login_invalid_sec"))
+                dialog.run()
+                dialog.hide()
+        else:
+            dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
+                                       type = MESSAGETYPE_ERROR,
+                                       message_format = xstr("login_failconn"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.format_secondary_markup(xstr("login_failconn_sec"))
+            
+            dialog.run()
+            dialog.hide()
+
+#-----------------------------------------------------------------------------
+
+class FlightSelectionPage(Page):
+    """The page to select the flight."""
+    def __init__(self, wizard):
+        """Construct the flight selection page."""
+        help = xstr("flightsel_help") 
+        completedHelp = xstr("flightsel_chelp")
+        super(FlightSelectionPage, self).__init__(wizard, xstr("flightsel_title"),
+                                                  help, completedHelp = completedHelp)
+
+
+        self._listStore = gtk.ListStore(str, str, str, str)
+        self._flightList = gtk.TreeView(self._listStore)
+        column = gtk.TreeViewColumn(xstr("flightsel_no"), gtk.CellRendererText(),
+                                    text = 1)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn(xstr("flightsel_deptime"), gtk.CellRendererText(),
+                                    text = 0)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn(xstr("flightsel_from"), gtk.CellRendererText(),
+                                    text = 2)
+        column.set_expand(True)
+        self._flightList.append_column(column)
+        column = gtk.TreeViewColumn(xstr("flightsel_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)
+        # FIXME: these should be constants in common.py
+        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.addNextButton(sensitive = False,
+                                          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._completed:
+            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._wizard.gui.enableFlightInfo()
+
+            self._updateDepartureGate()
+        
+    def _updateDepartureGate(self):
+        """Update the departure gate for the booked flight."""
+        flight = self._wizard._bookedFlight
+        if self._wizard.gui.config.onlineGateSystem:
+            if flight.departureICAO=="LHBP":
+                self._wizard.getFleet(self._fleetRetrieved)
+            else:
+                self._wizard.updatePlane(self._planeUpdated,
+                                         flight.tailNumber,
+                                         const.PLANE_AWAY)
+        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()
+
+    def _planeUpdated(self, success):
+        """Callback for the plane updating."""
+        self._nextDistance = 2
+        self._wizard.jumpPage(2)
+        
+#-----------------------------------------------------------------------------
+
+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."""
+        super(GateSelectionPage, self).__init__(wizard, xstr("gatesel_title"),
+                                                xstr("gatesel_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)        
+
+        self.addPreviousButton(clicked = self._backClicked)
+        
+        self._button = self.addNextButton(sensitive = False,
+                                          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._completed:
+            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)
+        else:
+            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(parent = self._wizard.gui.mainWindow,
+                                       type = MESSAGETYPE_ERROR,
+                                       message_format = xstr("gatesel_conflict"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.format_secondary_markup(xstr("gatesel_conflict_sec"))
+            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."
+        completedHelp = "The basic data of your flight can be read below."
+        super(ConnectPage, self).__init__(wizard, xstr("connect_title"),
+                                          xstr("connect_help"),
+                                          completedHelp = xstr("connect_chelp"))
+        
+        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(xstr("connect_flightno"))
+        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(7)
+        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(xstr("connect_acft"))
+        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(xstr("connect_tailno"))
+        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(10)
+        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(xstr("connect_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(6)
+        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(xstr("connect_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)
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addButton(xstr("button_connect"), default = True,
+                                      tooltip = xstr("button_connect_tooltip"))
+        self._clickedID = self._button.connect("clicked", self._connectClicked)
+
+    def activate(self):
+        """Setup the departure information."""
+        self._button.set_label(xstr("button_connect"))
+        self._button.set_use_underline(True)
+        self._button.set_tooltip_text(xstr("button_connect_tooltip"))
+        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(xstr("button_next"))
+        self._button.set_use_underline(True)
+        self._button.set_tooltip_text(xstr("button_next_tooltip"))
+        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."""
+        super(PayloadPage, self).__init__(wizard, xstr("payload_title"),
+                                          xstr("payload_help"),
+                                          completedHelp = xstr("payload_chelp"))
+
+        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(xstr("payload_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(xstr("payload_pax"))
+        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(xstr("payload_bag"))
+        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(xstr("payload_cargo"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 3, 4)
+
+        self._cargoWeight = IntegerEntry(defaultValue = 0)
+        self._cargoWeight.set_width_chars(6)
+        self._cargoWeight.connect("integer-changed", self._cargoWeightChanged)
+        self._cargoWeight.set_tooltip_text(xstr("payload_cargo_tooltip"))
+        table.attach(self._cargoWeight, 1, 2, 3, 4)
+        label.set_mnemonic_widget(self._cargoWeight)
+
+        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
+        
+        label = gtk.Label(xstr("payload_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>" + xstr("payload_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(xstr("payload_fszfw"))
+        self._zfwButton.set_use_underline(True)
+        self._zfwButton.connect("clicked", self._zfwRequested)
+        self._zfwButton.set_tooltip_text(xstr("payload_fszfw_tooltip"))
+        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.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight entered."""
+        return self._cargoWeight.get_int()
+
+    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._cargoWeight.set_int(bookedFlight.cargoWeight)
+        self._cargoWeight.set_sensitive(True)
+        self._mailWeight.set_text(str(bookedFlight.mailWeight))
+        self._simulatorZFW.set_text("-")
+        self._simulatorZFWValue = None
+        self._zfwButton.set_sensitive(True)
+        self._updateCalculatedZFW()
+
+    def finalize(self):
+        """Finalize the payload page."""
+        self._cargoWeight.set_sensitive(False)
+        self._wizard.gui.initializeWeightHelp()
+
+    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._cargoWeight.get_int()
+        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, weight):
+        """Called when the cargo weight has changed."""
+        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(xstr("payload_zfw_busy"))
+        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."""
+        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):
+        super(TimePage, self).__init__(wizard, xstr("time_title"),
+                                       xstr("time_help"),
+                                       completedHelp = xstr("time_chelp"))
+
+        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(xstr("time_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(xstr("time_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(xstr("time_fs"))
+        self._timeButton.set_use_underline(True)
+        self._timeButton.set_tooltip_text(xstr("time_fs_tooltip"))
+        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.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(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()))
+        self._simulatorTime.set_text("-")
+
+    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(xstr("time_busy"))
+        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."""
+        if not self._completed:
+            gui = self._wizard.gui
+            gui.beginBusy(xstr("fuel_get_busy"))
+            
+            gui.simulator.getFuel(gui.flight.aircraft.fuelTanks,
+                                  self._handleFuel)
+        else:
+            self._wizard.nextPage()
+
+    def _handleFuel(self, fuelData):
+        """Callback for the fuel query operation."""
+        gobject.idle_add(self._processFuel, fuelData)
+
+    def _processFuel(self, fuelData):
+        """Process the given fuel data."""
+        self._wizard.gui.endBusy()
+        self._wizard._fuelData = fuelData
+        self._wizard.nextPage()
+        
+#-----------------------------------------------------------------------------
+
+class FuelTank(gtk.VBox):
+    """Widget for the fuel tank."""
+    def __init__(self, fuelTank, name, capacity, currentWeight):
+        """Construct the widget for the tank with the given name."""
+        super(FuelTank, self).__init__()
+
+        self._enabled = True
+        self.fuelTank = fuelTank
+        self.capacity = capacity
+        self.currentWeight = currentWeight
+        self.expectedWeight = currentWeight
+
+        label = gtk.Label("<b>" + name + "</b>")
+        label.set_use_markup(True)
+        label.set_use_underline(True)
+        label.set_justify(JUSTIFY_CENTER)
+        label.set_alignment(0.5, 1.0)
+        self.pack_start(label, False, False, 4)
+
+        self._tankFigure = gtk.EventBox()
+        self._tankFigure.set_size_request(38, -1)
+        self._tankFigure.set_visible_window(False)
+        self._tankFigure.set_tooltip_markup(xstr("fuel_tank_tooltip"))
+
+        if pygobject:
+            self._tankFigure.connect("draw", self._drawTankFigure)
+        else:
+            self._tankFigure.connect("expose_event", self._drawTankFigure)
+        self._tankFigure.connect("button_press_event", self._buttonPressed)
+        self._tankFigure.connect("motion_notify_event", self._motionNotify)
+        self._tankFigure.connect("scroll-event", self._scrolled)
+        
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 1.0)
+        alignment.add(self._tankFigure)
+
+        self.pack_start(alignment, True, True, 4)
+
+        self._expectedButton = gtk.SpinButton()
+        self._expectedButton.set_numeric(True)
+        self._expectedButton.set_range(0, self.capacity)
+        self._expectedButton.set_increments(10, 100)
+        self._expectedButton.set_value(currentWeight)
+        self._expectedButton.set_alignment(1.0)
+        self._expectedButton.set_width_chars(5)
+        self._expectedButton.connect("value-changed", self._expectedChanged)
+
+        label.set_mnemonic_widget(self._expectedButton)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 1.0)
+        alignment.add(self._expectedButton) 
+        self.pack_start(alignment, False, False, 4)
+
+    def setCurrent(self, currentWeight):
+        """Set the current weight."""
+        self.currentWeight = currentWeight
+        self._redraw()
+
+    def isCorrect(self):
+        """Determine if the contents of the fuel tank are as expected"""
+        return abs(self.expectedWeight - self.currentWeight)<=1
+
+    def disable(self):
+        """Disable the fuel tank."""
+        self._expectedButton.set_sensitive(False)
+        self._enabled = False
+
+    def _redraw(self):
+        """Redraw the tank figure."""
+        self._tankFigure.queue_draw()
+
+    def _drawTankFigure(self, tankFigure, eventOrContext):
+        """Draw the tank figure."""
+        triangleSize = 5
+
+        context = eventOrContext if pygobject else tankFigure.window.cairo_create()
+        (xOffset, yOffset) = (0, 0) if pygobject \
+                             else (tankFigure.allocation.x, tankFigure.allocation.y)
+        
+        width = tankFigure.get_allocated_width() if pygobject \
+                else tankFigure.allocation.width
+        height = tankFigure.get_allocated_height() if pygobject \
+                 else tankFigure.allocation.height
+
+        rectangleX0 = triangleSize
+        rectangleY0 = triangleSize
+        rectangleX1 = width - 1 - triangleSize
+        rectangleY1 = height - 1 - triangleSize
+        rectangleLineWidth = 2.0
+
+        context.set_source_rgb(0.0, 0.0, 0.0)
+        context.set_line_width(rectangleLineWidth)
+        context.rectangle(xOffset + rectangleX0 + rectangleLineWidth/2,
+                          yOffset + rectangleY0 + rectangleLineWidth/2,
+                          rectangleX1 - rectangleX0 - rectangleLineWidth,
+                          rectangleY1 - rectangleY0 - rectangleLineWidth)
+        context.stroke()
+
+        rectangleInnerLeft   = rectangleX0 + rectangleLineWidth
+        rectangleInnerRight  = rectangleX1 - rectangleLineWidth
+        self._rectangleInnerTop = rectangleInnerTop = rectangleY0 + rectangleLineWidth
+        self._rectangleInnerBottom = rectangleInnerBottom = rectangleY1 - rectangleLineWidth
+
+        rectangleInnerWidth = rectangleInnerRight - rectangleInnerLeft
+        rectangleInnerHeight = rectangleInnerBottom - rectangleInnerTop
+
+        context.set_source_rgb(1.0, 0.9, 0.6)
+        currentHeight = self.currentWeight * rectangleInnerHeight / self.capacity
+        currentX = rectangleInnerTop + rectangleInnerHeight - currentHeight
+        context.rectangle(xOffset + rectangleInnerLeft,
+                          yOffset + rectangleInnerTop +
+                          rectangleInnerHeight - currentHeight,
+                          rectangleInnerWidth, currentHeight)
+        context.fill()
+
+        expectedHeight = self.expectedWeight * rectangleInnerHeight / self.capacity
+        expectedY = rectangleInnerTop + rectangleInnerHeight - expectedHeight
+
+        context.set_line_width(1.5)
+        context.set_source_rgb(0.0, 0.85, 0.85)
+        context.move_to(xOffset + rectangleX0, yOffset + expectedY)
+        context.line_to(xOffset + rectangleX1, yOffset + expectedY)
+        context.stroke()
+
+        context.set_line_width(0.0)
+        context.move_to(xOffset + 0, yOffset + expectedY - triangleSize)
+        context.line_to(xOffset + 0, yOffset + expectedY + triangleSize)
+        context.line_to(xOffset + rectangleX0 + 1, yOffset + expectedY)
+        context.line_to(xOffset + 0, yOffset + expectedY - triangleSize)
+        context.fill()
+
+        context.set_line_width(0.0)
+        context.move_to(xOffset + width, yOffset + expectedY - triangleSize)
+        context.line_to(xOffset + width, yOffset + expectedY + triangleSize)
+        context.line_to(xOffset + rectangleX1 - 1, yOffset + expectedY)
+        context.line_to(xOffset + width, yOffset + expectedY - triangleSize)
+        context.fill()
+
+        return True
+
+    def _setExpectedFromY(self, y):
+        """Set the expected weight from the given Y-coordinate."""
+        level = (self._rectangleInnerBottom - y) / \
+                (self._rectangleInnerBottom - self._rectangleInnerTop)
+        level = min(1.0, max(0.0, level))
+        self._expectedButton.set_value(level * self.capacity)
+        
+    def _buttonPressed(self, tankFigure, event):
+        """Called when a button is pressed in the figure.
+
+        The expected level will be set there."""
+        if self._enabled and event.button==1:
+            self._setExpectedFromY(event.y)
+        
+    def _motionNotify(self, tankFigure, event):
+        """Called when the mouse pointer moves within the area of a tank figure."""
+        if self._enabled and event.state==BUTTON1_MASK:            
+            self._setExpectedFromY(event.y)
+
+    def _scrolled(self, tankFigure, event):
+        """Called when a scroll event is received."""
+        if self._enabled:
+            increment = 1 if event.state==CONTROL_MASK \
+                        else 100 if event.state==SHIFT_MASK \
+                        else 10 if event.state==0 else 0
+            if increment!=0:
+                if event.direction==SCROLL_DOWN:
+                    increment *= -1
+                self._expectedButton.spin(SPIN_USER_DEFINED, increment)
+        
+    def _expectedChanged(self, spinButton):
+        """Called when the expected value has changed."""
+        self.expectedWeight = spinButton.get_value_as_int()
+        self._redraw()        
+
+#-----------------------------------------------------------------------------
+
+class FuelPage(Page):
+    """The page containing the fuel tank filling."""
+    _pumpStep = 0.02
+    
+    def __init__(self, wizard):
+        """Construct the page."""
+        super(FuelPage, self).__init__(wizard, xstr("fuel_title"),
+                                       xstr("fuel_help"),
+                                       completedHelp = xstr("fuel_chelp"))
+
+        self._fuelTanks = []
+        self._fuelTable = None
+        self._fuelAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                            xscale = 0.0, yscale = 1.0)
+        self.setMainWidget(self._fuelAlignment)
+
+        tanks = acft.MostFuelTankAircraft.fuelTanks
+        tankData = ((2500, 3900),) * len(tanks)
+        self._setupTanks(tanks, tankData)
+
+        self._backButton = self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+        self._pumpIndex = 0
+
+    def activate(self):
+        """Activate the page."""
+        gui = self._wizard.gui
+
+        self._setupTanks(gui.flight.aircraft.fuelTanks,
+                         self._wizard._fuelData)
+
+    def finalize(self):
+        """Finalize the page."""
+        for fuelTank in self._fuelTanks:
+            fuelTank.disable()
+
+    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._completed:
+            self._pumpIndex = 0
+            self._wizard.gui.beginBusy(xstr("fuel_pump_busy"))
+            self._pump()
+        else:
+            self._wizard.nextPage()        
+
+    def _setupTanks(self, tanks, tankData):
+        """Setup the tanks for the given data."""
+        numTanks = len(tanks)
+        if self._fuelTable is not None:
+            self._fuelAlignment.remove(self._fuelTable)
+
+        self._fuelTanks = []
+        self._fuelTable = gtk.Table(numTanks, 1)
+        self._fuelTable.set_col_spacings(16)
+        for i in range(0, numTanks):
+            tank = tanks[i]
+            (current, capacity) = tankData[i]
+
+            fuelTank = FuelTank(tank,
+                                xstr("fuel_tank_" +
+                                     const.fuelTank2string(tank)),
+                                capacity, current)
+            self._fuelTable.attach(fuelTank, i, i+1, 0, 1)
+            self._fuelTanks.append(fuelTank)
+            
+        self._fuelAlignment.add(self._fuelTable)
+        self.show_all()
+
+    def _pump(self):
+        """Perform one step of pumping.
+
+        It is checked, if the current tank's contents are of the right
+        quantity. If not, it is filled one step further to the desired
+        contents. Otherwise the next tank is started. If all tanks are are
+        filled, the next page is selected."""
+        numTanks = len(self._fuelTanks)
+
+        fuelTank = None
+        while self._pumpIndex < numTanks:
+            fuelTank = self._fuelTanks[self._pumpIndex]
+            if fuelTank.isCorrect():
+                self._pumpIndex += 1
+                fuelTank = None
+            else:
+                break
+
+        if fuelTank is None:
+            self._wizard.gui.endBusy()
+            self._wizard.nextPage()
+        else:
+            currentLevel = fuelTank.currentWeight / fuelTank.capacity
+            expectedLevel = fuelTank.expectedWeight / fuelTank.capacity
+            if currentLevel<expectedLevel:
+                currentLevel += FuelPage._pumpStep
+                if currentLevel>expectedLevel: currentLevel = expectedLevel
+            else:
+                currentLevel -= FuelPage._pumpStep
+                if currentLevel<expectedLevel: currentLevel = expectedLevel
+            fuelTank.setCurrent(currentLevel * fuelTank.capacity)
+            self._wizard.gui.simulator.setFuelLevel([(fuelTank.fuelTank,
+                                                      currentLevel)])
+            gobject.timeout_add(50, self._pump)
+        
+#-----------------------------------------------------------------------------
+
+class RoutePage(Page):
+    """The page containing the route and the flight level."""
+    def __init__(self, wizard):
+        """Construct the page."""
+        super(RoutePage, self).__init__(wizard, xstr("route_title"),
+                                        xstr("route_help"),
+                                        completedHelp = xstr("route_chelp"))
+
+        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(xstr("route_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_tooltip_text(xstr("route_level_tooltip"))
+        self._cruiseLevel.set_numeric(True)
+        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
+        label.set_mnemonic_widget(self._cruiseLevel)
+        self._filedCruiseLevel = 240
+
+        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(xstr("route_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._uppercasingRoute = False
+
+        self._route = gtk.TextView()
+        self._route.set_tooltip_text(xstr("route_route_tooltip"))
+        self._route.set_wrap_mode(WRAP_WORD)
+        self._route.get_buffer().connect("changed", self._routeChanged)
+        self._route.get_buffer().connect_after("insert-text", self._routeInserted)
+        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.addPreviousButton(clicked = self._backClicked)        
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def filedCruiseLevel(self):
+        """Get the filed cruise level."""
+        return self._filedCruiseLevel
+
+    @property
+    def cruiseLevel(self):
+        """Get the cruise level."""
+        return self._cruiseLevel.get_value_as_int()
+
+    @property
+    def route(self):
+        """Get the route."""
+        return self._getRoute()
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._cruiseLevel.set_value(240)
+        self._filedCruiseLevel = 240
+        self._route.get_buffer().set_text(self._wizard._bookedFlight.route)
+        self._updateForwardButton()
+
+    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."""
+        if not self._uppercasingRoute:
+            self._updateForwardButton()
+
+    def _routeInserted(self, textBuffer, iter, text, length):
+        """Called when new characters are inserted into the route.
+
+        It uppercases all characters."""
+        if not self._uppercasingRoute:
+            self._uppercasingRoute = True
+
+            iter1 = iter.copy()
+            iter1.backward_chars(length)
+            textBuffer.delete(iter, iter1)
+
+            textBuffer.insert(iter, text.upper())
+
+            self._uppercasingRoute = False
+
+    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._completed:
+            self._wizard.nextPage()
+        else:
+            bookedFlight = self._wizard._bookedFlight
+            self._filedCruiseLevel = self.cruiseLevel
+            self._wizard.gui.beginBusy(xstr("route_down_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(xstr("route_down_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 = xstr("briefing_title") % (1 if departure else 2,
+                                          xstr("briefing_departure")
+                                          if departure
+                                          else xstr("briefing_arrival"))
+        super(BriefingPage, self).__init__(wizard, title, xstr("briefing_help"),
+                                           completedHelp = xstr("briefing_chelp"))
+
+        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(xstr("briefing_notams_init"))
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.set_size_request(-1, 128)
+        # FIXME: these constants should be in common
+        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(xstr("briefing_metar_init"))
+        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._uppercasingMETAR = False
+
+        self._metar = gtk.TextView()
+        self._metar.set_accepts_tab(False)
+        self._metar.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
+        self._metar.get_buffer().connect("changed", self._metarChanged)
+        self._metar.get_buffer().connect_after("insert-text", self._metarInserted)
+        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)
+        self.metarEdited = False
+
+        self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def metar(self):
+        """Get the METAR on the page."""
+        buffer = self._metar.get_buffer()
+        return buffer.get_text(buffer.get_start_iter(),
+                               buffer.get_end_iter(), True)        
+
+    def setMETAR(self, metar):
+        """Set the metar."""
+        self._metar.get_buffer().set_text(metar)
+        self.metarEdited = False
+
+    def activate(self):
+        """Activate the page."""
+        if not self._departure:
+            self._button.set_label(xstr("briefing_button"))
+            self._button.set_has_tooltip(False)
+            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(xstr("briefing_notams_template") % (icao,))
+        buffer = self._notams.get_buffer()
+        if notams is None:
+            buffer.set_text(xstr("briefing_notams_failed"))
+        elif not notams:
+            buffer.set_text(xstr("briefing_notams_missing"))
+        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(xstr("briefing_metar_template") % (icao,))
+        buffer = self._metar.get_buffer()
+        if metar is None:
+            buffer.set_text(xstr("briefing_metar_failed"))
+        else:
+            buffer.set_text(metar)
+
+        label = self._metarFrame.get_label_widget()
+        label.set_use_underline(True)
+        label.set_mnemonic_widget(self._metar)
+
+        self.metarEdited = False
+
+    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._completed:
+                self._wizard.gui.startMonitoring()
+                self._button.set_label(xstr("button_next"))
+                self._button.set_tooltip_text(xstr("button_next_tooltip"))
+                self.complete()
+
+        self._wizard.nextPage()
+
+    def _metarChanged(self, buffer):
+        """Called when the METAR has changed."""
+        if not self._uppercasingMETAR:
+            self.metarEdited = True
+            self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
+                                                       buffer.get_end_iter(),
+                                                       True)!="")
+
+    def _metarInserted(self, textBuffer, iter, text, length):
+        """Called when new characters are inserted into the METAR.
+
+        It uppercases all characters."""
+        if not self._uppercasingMETAR:
+            self._uppercasingMETAR = True
+
+            iter1 = iter.copy()
+            iter1.backward_chars(length)
+            textBuffer.delete(iter, iter1)
+
+            textBuffer.insert(iter, text.upper())
+
+            self._uppercasingMETAR = False
+
+#-----------------------------------------------------------------------------
+
+class TakeoffPage(Page):
+    """Page for entering the takeoff data."""
+    def __init__(self, wizard):
+        """Construct the takeoff page."""
+        super(TakeoffPage, self).__init__(wizard, xstr("takeoff_title"),
+                                          xstr("takeoff_help"),
+                                          completedHelp = xstr("takeoff_chelp"))
+
+        self._forwardAllowed = False
+
+        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)
+
+        label = gtk.Label(xstr("takeoff_runway"))
+        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(xstr("takeoff_runway_tooltip"))
+        self._runway.connect("changed", self._upperChanged)
+        table.attach(self._runway, 1, 3, 0, 1)
+        label.set_mnemonic_widget(self._runway)
+        
+        label = gtk.Label(xstr("takeoff_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(xstr("takeoff_sid_tooltip"))
+        self._sid.connect("changed", self._upperChanged)
+        table.attach(self._sid, 1, 3, 1, 2)
+        label.set_mnemonic_widget(self._sid)
+        
+        label = gtk.Label(xstr("takeoff_v1"))
+        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 = IntegerEntry()
+        self._v1.set_width_chars(4)
+        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip"))
+        self._v1.connect("integer-changed", self._valueChanged)
+        table.attach(self._v1, 2, 3, 2, 3)
+        label.set_mnemonic_widget(self._v1)
+        
+        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 2, 3)
+        
+        label = gtk.Label(xstr("takeoff_vr"))
+        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 = IntegerEntry()
+        self._vr.set_width_chars(4)
+        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip"))
+        self._vr.connect("integer-changed", self._valueChanged)
+        table.attach(self._vr, 2, 3, 3, 4)
+        label.set_mnemonic_widget(self._vr)
+        
+        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 3, 4)
+        
+        label = gtk.Label(xstr("takeoff_v2"))
+        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 = IntegerEntry()
+        self._v2.set_width_chars(4)
+        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip"))
+        self._v2.connect("integer-changed", self._valueChanged)
+        table.attach(self._v2, 2, 3, 4, 5)
+        label.set_mnemonic_widget(self._v2)
+        
+        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 4, 5)
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def runway(self):
+        """Get the runway."""
+        return self._runway.get_text()
+
+    @property
+    def sid(self):
+        """Get the SID."""
+        return self._sid.get_text()
+
+    @property
+    def v1(self):
+        """Get the v1 speed."""
+        return self._v1.get_int()
+
+    @property
+    def vr(self):
+        """Get the vr speed."""
+        return self._vr.get_int()
+
+    @property
+    def v2(self):
+        """Get the v2 speed."""
+        return self._v2.get_int()
+
+    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_int(None)
+        self._v1.set_sensitive(True)
+        self._vr.set_int(None)
+        self._vr.set_sensitive(True)
+        self._v2.set_int(None)
+        self._v2.set_sensitive(True)
+        self._button.set_sensitive(False)
+        
+    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)
+        self._wizard.gui.flight.aircraft.updateV1R2()
+
+    def allowForward(self):
+        """Allow going to the next page."""
+        self._forwardAllowed = True
+        self._updateForwardButton()
+
+    def _updateForwardButton(self):
+        """Update the sensitivity of the forward button based on some conditions."""
+        sensitive = self._forwardAllowed and \
+                    self._runway.get_text()!="" and \
+                    self._sid.get_text()!="" and \
+                    self.v1 is not None and \
+                    self.vr is not None and \
+                    self.v2 is not None and \
+                    self.v1 <= self.vr and \
+                    self.vr <= self.v2
+        self._button.set_sensitive(sensitive)
+
+    def _valueChanged(self, widget, arg = None):
+        """Called when the value of some widget has changed."""
+        self._updateForwardButton()
+        
+    def _upperChanged(self, entry, arg = None):
+        """Called when the value of some entry widget has changed and the value
+        should be converted to uppercase."""
+        entry.set_text(entry.get_text().upper())
+        self._valueChanged(entry, arg)
+        
+    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."""
+        super(LandingPage, self).__init__(wizard, xstr("landing_title"),
+                                          xstr("landing_help"),
+                                          completedHelp = xstr("landing_chelp"))
+
+        self._flightEnded = False
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(5, 5)
+        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(xstr("landing_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(xstr("landing_star_tooltip"))
+        self._star.connect("changed", self._upperChanged)
+        self._star.set_sensitive(False)
+        table.attach(self._star, 2, 4, 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(xstr("landing_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(xstr("landing_transition_tooltip"))
+        self._transition.connect("changed", self._upperChanged)
+        self._transition.set_sensitive(False)
+        table.attach(self._transition, 2, 4, 1, 2)
+        label.set_mnemonic_widget(self._transitionButton)
+
+        label = gtk.Label(xstr("landing_runway"))
+        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(xstr("landing_runway_tooltip"))
+        self._runway.connect("changed", self._upperChanged)
+        table.attach(self._runway, 2, 4, 2, 3)
+        label.set_mnemonic_widget(self._runway)
+
+        label = gtk.Label(xstr("landing_approach"))
+        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(xstr("landing_approach_tooltip"))
+        self._approachType.connect("changed", self._upperChanged)
+        table.attach(self._approachType, 2, 4, 3, 4)
+        label.set_mnemonic_widget(self._approachType)
+
+        label = gtk.Label(xstr("landing_vref"))
+        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 = IntegerEntry()
+        self._vref.set_width_chars(5)
+        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip"))
+        self._vref.connect("integer-changed", self._vrefChanged)
+        table.attach(self._vref, 3, 4, 5, 6)
+        label.set_mnemonic_widget(self._vref)
+        
+        table.attach(gtk.Label(xstr("label_knots")), 4, 5, 5, 6)
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+        # These are needed for correct size calculations
+        self._starButton.set_active(True)
+        self._transitionButton.set_active(True)
+
+    @property
+    def star(self):
+        """Get the STAR or None if none entered."""
+        return self._star.get_text() if self._starButton.get_active() else None
+
+    @property
+    def transition(self):
+        """Get the transition or None if none entered."""
+        return self._transition.get_text() \
+               if self._transitionButton.get_active() else None
+
+    @property
+    def approachType(self):
+        """Get the approach type."""
+        return self._approachType.get_text()
+
+    @property
+    def runway(self):
+        """Get the runway."""
+        return self._runway.get_text()
+
+    @property
+    def vref(self):
+        """Return the landing reference speed."""
+        return self._vref.get_int()
+
+    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_int(None)
+        self._vref.set_sensitive(True)
+
+        self._updateForwardButton()
+
+    def flightEnded(self):
+        """Called when the flight has ended."""
+        self._flightEnded = 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)
+        self._wizard.gui.flight.aircraft.updateVRef()
+        # FIXME: Perhaps a separate initialize() call which would set up
+        # defaults? -> use reset()
+        self._flightEnded = 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):
+        """Update the sensitivity of the forward button."""
+        sensitive = self._flightEnded and \
+                    (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()!="" and \
+                    self.vref is not None
+        self._button.set_sensitive(sensitive)
+
+    def _upperChanged(self, entry):
+        """Called for entry widgets that must be converted to uppercase."""
+        entry.set_text(entry.get_text().upper())
+        self._updateForwardButton()
+
+    def _vrefChanged(self, widget, value):
+        """Called when the Vref 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._wizard.gui.config.onlineGateSystem and \
+           not self._completed and \
+           self._wizard.bookedFlight.arrivalICAO=="LHBP":
+            self._wizard.getFleet(callback = self._fleetRetrieved,
+                                  force = True)
+        else:
+            self._wizard.nextPage()
+
+    def _fleetRetrieved(self, fleet):
+        """Callback for the fleet retrieval."""
+        self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+class FinishPage(Page):
+    """Flight finish page."""
+    _flightTypes = [ ("flighttype_scheduled", const.FLIGHTTYPE_SCHEDULED),
+                     ("flighttype_ot", const.FLIGHTTYPE_OLDTIMER),
+                     ("flighttype_vip", const.FLIGHTTYPE_VIP),
+                     ("flighttype_charter", const.FLIGHTTYPE_CHARTER) ]
+    
+    def __init__(self, wizard):
+        """Construct the finish page."""
+        super(FinishPage, self).__init__(wizard, xstr("finish_title"),
+                                         xstr("finish_help"))
+        
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(8, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_rating"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 0, 1)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._flightRating = gtk.Label()
+        self._flightRating.set_width_chars(8)
+        self._flightRating.set_alignment(0.0, 0.5)
+        self._flightRating.set_use_markup(True)
+        labelAlignment.add(self._flightRating)
+        table.attach(labelAlignment, 1, 2, 0, 1)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_flight_time"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 1, 2)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._flightTime = gtk.Label()
+        self._flightTime.set_width_chars(10)
+        self._flightTime.set_alignment(0.0, 0.5)
+        self._flightTime.set_use_markup(True)
+        labelAlignment.add(self._flightTime)
+        table.attach(labelAlignment, 1, 2, 1, 2)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_block_time"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 2, 3)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._blockTime = gtk.Label()
+        self._blockTime.set_width_chars(10)
+        self._blockTime.set_alignment(0.0, 0.5)
+        self._blockTime.set_use_markup(True)
+        labelAlignment.add(self._blockTime)
+        table.attach(labelAlignment, 1, 2, 2, 3)
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_distance"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 3, 4)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._distanceFlown = gtk.Label()
+        self._distanceFlown.set_width_chars(10)
+        self._distanceFlown.set_alignment(0.0, 0.5)
+        self._distanceFlown.set_use_markup(True)
+        labelAlignment.add(self._distanceFlown)
+        table.attach(labelAlignment, 1, 2, 3, 4)
+        
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_fuel"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 4, 5)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._fuelUsed = gtk.Label()
+        self._fuelUsed.set_width_chars(10)
+        self._fuelUsed.set_alignment(0.0, 0.5)
+        self._fuelUsed.set_use_markup(True)
+        labelAlignment.add(self._fuelUsed)
+        table.attach(labelAlignment, 1, 2, 4, 5)
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, xscale = 0.0,
+                                       yalign = 0.5, yscale = 0.0)
+        label = gtk.Label(xstr("finish_type"))
+        label.set_use_underline(True)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 5, 6)
+
+        flightTypeModel = gtk.ListStore(str, int)
+        for (name, type) in FinishPage._flightTypes:
+            flightTypeModel.append([xstr(name), type])
+
+        self._flightType = gtk.ComboBox(model = flightTypeModel)
+        renderer = gtk.CellRendererText()
+        self._flightType.pack_start(renderer, True)
+        self._flightType.add_attribute(renderer, "text", 0)
+        self._flightType.set_tooltip_text(xstr("finish_type_tooltip"))
+        self._flightType.set_active(0)
+        self._flightType.connect("changed", self._flightTypeChanged)
+        flightTypeAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        flightTypeAlignment.add(self._flightType)
+        table.attach(flightTypeAlignment, 1, 2, 5, 6)
+        label.set_mnemonic_widget(self._flightType)        
+
+        self._onlineFlight = gtk.CheckButton(xstr("finish_online"))
+        self._onlineFlight.set_use_underline(True)
+        self._onlineFlight.set_tooltip_text(xstr("finish_online_tooltip"))
+        onlineFlightAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        onlineFlightAlignment.add(self._onlineFlight)
+        table.attach(onlineFlightAlignment, 1, 2, 6, 7)
+
+        labelAlignment = gtk.Alignment(xalign = 1.0, xscale = 0.0,
+                                       yalign = 0.5, yscale = 0.0)
+        self._gateLabel = gtk.Label(xstr("finish_gate"))
+        self._gateLabel.set_use_underline(True)
+        labelAlignment.add(self._gateLabel)
+        table.attach(labelAlignment, 0, 1, 7, 8)
+
+        self._gatesModel = gtk.ListStore(str)
+
+        self._gate = gtk.ComboBox(model = self._gatesModel)
+        renderer = gtk.CellRendererText()
+        self._gate.pack_start(renderer, True)
+        self._gate.add_attribute(renderer, "text", 0)
+        self._gate.set_tooltip_text(xstr("finish_gate_tooltip"))
+        self._gate.connect("changed", self._gateChanged)               
+        gateAlignment = gtk.Alignment(xalign=0.0, xscale=1.0)
+        gateAlignment.add(self._gate)
+        table.attach(gateAlignment, 1, 2, 7, 8)
+        self._gateLabel.set_mnemonic_widget(self._gate)
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._saveButton = self.addButton(xstr("finish_save"),
+                                          sensitive = False,
+                                          clicked = self._saveClicked,
+                                          tooltip = xstr("finish_save_tooltip"))
+        self._savePIREPDialog = None
+        self._lastSavePath = None
+        
+        self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
+                                          sensitive = False,
+                                          clicked = self._sendClicked,
+                                          tooltip = xstr("sendPIREP_tooltip"))
+        
+    @property
+    def flightType(self):
+        """Get the flight type."""
+        index = self._flightType.get_active()
+        return None if index<0 else self._flightType.get_model()[index][1]
+
+    @property
+    def online(self):
+        """Get whether the flight was an online flight or not."""
+        return self._onlineFlight.get_active()
+
+    def activate(self):
+        """Activate the page."""
+        flight = self._wizard.gui._flight
+        rating = flight.logger.getRating()
+        if rating<0:
+            self._flightRating.set_markup('<b><span foreground="red">NO GO</span></b>')
+        else:
+            self._flightRating.set_markup("<b>%.1f %%</b>" % (rating,))
+
+        flightLength = flight.flightTimeEnd - flight.flightTimeStart
+        self._flightTime.set_markup("<b>%s</b>" % \
+                                    (util.getTimeIntervalString(flightLength),))
+        
+        blockLength = flight.blockTimeEnd - flight.blockTimeStart
+        self._blockTime.set_markup("<b>%s</b>" % \
+                                   (util.getTimeIntervalString(blockLength),))
+
+        self._distanceFlown.set_markup("<b>%.2f NM</b>" % \
+                                       (flight.flownDistance,))
+        
+        self._fuelUsed.set_markup("<b>%.0f kg</b>" % \
+                                  (flight.startFuel - flight.endFuel,))
+
+        self._flightType.set_active(-1)
+        self._onlineFlight.set_active(True)
+
+        self._gatesModel.clear()
+        if self._wizard.gui.config.onlineGateSystem and \
+           self._wizard.bookedFlight.arrivalICAO=="LHBP":
+            occupiedGates = self._wizard._fleet.getOccupiedGateNumbers()
+            for gateNumber in const.lhbpGateNumbers:
+                if gateNumber not in occupiedGates:
+                    self._gatesModel.append([gateNumber])
+            self._gateLabel.set_sensitive(True)
+            self._gate.set_sensitive(True)
+            self._gate.set_active(-1)
+        else:
+            self._gateLabel.set_sensitive(False)
+            self._gate.set_sensitive(False)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _updateButtons(self):
+        """Update the sensitivity state of the buttons."""
+        sensitive = self._flightType.get_active()>=0 and \
+                    (self._gatesModel.get_iter_first() is None or
+                     self._gate.get_active()>=0)
+        
+        self._saveButton.set_sensitive(sensitive)
+        self._sendButton.set_sensitive(sensitive)        
+
+    def _flightTypeChanged(self, comboBox):
+        """Called when the flight type has changed."""
+        self._updateButtons()
+
+    def _gateChanged(self, comboBox):
+        """Called when the arrival gate has changed."""
+        self._updateButtons()
+
+    def _saveClicked(self, button):
+        """Called when the Save PIREP button is clicked."""
+        gui = self._wizard.gui
+
+        bookedFlight = gui.bookedFlight
+        tm = time.gmtime()
+        
+        fileName = "%s %s %02d%02d %s-%s.pirep" % \
+                   (gui.loginResult.pilotID,
+                    str(bookedFlight.departureTime.date()),
+                    tm.tm_hour, tm.tm_min,
+                    bookedFlight.departureICAO,
+                    bookedFlight.arrivalICAO)
+
+        dialog = self._getSaveDialog()
+
+        if self._lastSavePath is None:
+            pirepDirectory = gui.config.pirepDirectory
+            if pirepDirectory is not None:
+                dialog.set_current_folder(pirepDirectory)
+        else:
+            dialog.set_current_folder(os.path.dirname(self._lastSavePath))
+            
+        dialog.set_current_name(fileName)
+        result = dialog.run()
+        dialog.hide()
+
+        if result==RESPONSETYPE_OK:
+            pirep = PIREP(gui)
+
+            self._lastSavePath = text2unicode(dialog.get_filename())
+            
+            if pirep.save(self._lastSavePath):
+                type = MESSAGETYPE_INFO
+                message = xstr("finish_save_done")
+                secondary = None
+            else:
+                type = MESSAGETYPE_ERROR
+                message = xstr("finish_save_failed")
+                secondary = xstr("finish_save_failed_sec")
+                
+            dialog = gtk.MessageDialog(parent = gui.mainWindow,
+                                       type = type, message_format = message)
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            if secondary is not None:
+                dialog.format_secondary_markup(secondary)
+
+            dialog.run()
+            dialog.hide()
+
+    def _getSaveDialog(self):
+        """Get the PIREP saving dialog.
+
+        If it does not exist yet, create it."""
+        if self._savePIREPDialog is None:
+            gui = self._wizard.gui
+            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                           xstr("finish_save_title"),
+                                           action = FILE_CHOOSER_ACTION_SAVE,
+                                           buttons = (gtk.STOCK_CANCEL,
+                                                      RESPONSETYPE_CANCEL,
+                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
+                                           parent = gui.mainWindow)
+            dialog.set_modal(True)
+            dialog.set_do_overwrite_confirmation(True)
+            
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("loadPIREP_filter_pireps"))
+            filter.add_pattern("*.pirep")
+            dialog.add_filter(filter)
+            
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("loadPIREP_filter_all"))
+            filter.add_pattern("*.*")
+            dialog.add_filter(filter)
+
+            self._savePIREPDialog = dialog
+
+        return self._savePIREPDialog
+        
+
+    def _sendClicked(self, button):
+        """Called when the Send button is clicked."""
+        pirep = PIREP(self._wizard.gui)
+        self._wizard.gui.sendPIREP(pirep,
+                                   callback = self._handlePIREPSent)
+
+    def _handlePIREPSent(self, returned, result):
+        """Callback for the PIREP sending result."""
+        if self._wizard.gui.config.onlineGateSystem and returned and result.success:
+            bookedFlight = self._wizard.bookedFlight
+            if bookedFlight.arrivalICAO=="LHBP":
+                iter = self._gate.get_active_iter()                
+                gateNumber = None if iter is None \
+                             else self._gatesModel.get_value(iter, 0)
+                
+                status = const.PLANE_PARKING if gateNumber is None \
+                         else const.PLANE_HOME
+            else:
+                gateNumber = None
+                status = const.PLANE_AWAY
+
+            self._wizard.updatePlane(self._planeUpdated,
+                                     bookedFlight.tailNumber,
+                                     status, gateNumber = gateNumber)
+
+    def _planeUpdated(self, success):
+        """Callback for the plane updating."""
+        pass
+        
+#-----------------------------------------------------------------------------
+
+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._payloadPage = PayloadPage(self) 
+        self._pages.append(self._payloadPage)
+        self._payloadIndex = len(self._pages)
+        self._pages.append(TimePage(self))
+        self._pages.append(FuelPage(self))
+        self._routePage = RoutePage(self)
+        self._pages.append(self._routePage)
+        self._departureBriefingPage = BriefingPage(self, True)
+        self._pages.append(self._departureBriefingPage)
+        self._arrivalBriefingPage = BriefingPage(self, False)
+        self._pages.append(self._arrivalBriefingPage)
+        self._arrivalBriefingIndex = len(self._pages)
+        self._takeoffPage = TakeoffPage(self) 
+        self._pages.append(self._takeoffPage)
+        self._landingPage = LandingPage(self) 
+        self._pages.append(self._landingPage)
+        self._finishPage = FinishPage(self)
+        self._pages.append(self._finishPage)
+        
+        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._completed:
+                page.complete()
+            self.remove(page)
+
+        self._currentPage = index
+        page = self._pages[index]
+        self.add(page)
+        if page._fromPage is None:
+            page._fromPage = fromPage
+            page.initialize()
+        self.show_all()
+        if fromPage is not None:
+            self.grabDefault()
+
+    @property
+    def bookedFlight(self):
+        """Get the booked flight selected."""
+        return self._bookedFlight
+
+    @property
+    def cargoWeight(self):
+        """Get the calculated ZFW value."""
+        return self._payloadPage.cargoWeight
+
+    @property
+    def zfw(self):
+        """Get the calculated ZFW value."""
+        return 0 if self._bookedFlight is None \
+               else self._payloadPage.calculateZFW()
+
+    @property
+    def filedCruiseAltitude(self):
+        """Get the filed cruise altitude."""
+        return self._routePage.filedCruiseLevel * 100
+
+    @property
+    def cruiseAltitude(self):
+        """Get the cruise altitude."""
+        return self._routePage.cruiseLevel * 100
+
+    @property
+    def route(self):
+        """Get the route."""
+        return self._routePage.route
+
+    @property
+    def departureMETAR(self):
+        """Get the METAR of the departure airport."""
+        return self._departureBriefingPage.metar
+
+    @property
+    def arrivalMETAR(self):
+        """Get the METAR of the arrival airport."""
+        return self._arrivalBriefingPage.metar
+
+    @property
+    def departureRunway(self):
+        """Get the departure runway."""
+        return self._takeoffPage.runway
+
+    @property
+    def sid(self):
+        """Get the SID."""
+        return self._takeoffPage.sid
+
+    @property
+    def v1(self):
+        """Get the V1 speed."""
+        return self._takeoffPage.v1
+
+    @property
+    def vr(self):
+        """Get the Vr speed."""
+        return self._takeoffPage.vr
+
+    @property
+    def v2(self):
+        """Get the V2 speed."""
+        return self._takeoffPage.v2
+
+    @property
+    def arrivalRunway(self):
+        """Get the arrival runway."""
+        return self._landingPage.runway
+
+    @property
+    def star(self):
+        """Get the STAR."""
+        return self._landingPage.star
+
+    @property
+    def transition(self):
+        """Get the transition."""
+        return self._landingPage.transition
+
+    @property
+    def approachType(self):
+        """Get the approach type."""
+        return self._landingPage.approachType
+
+    @property
+    def vref(self):
+        """Get the Vref speed."""
+        return self._landingPage.vref
+
+    @property
+    def flightType(self):
+        """Get the flight type."""
+        return self._finishPage.flightType
+
+    @property
+    def online(self):
+        """Get whether the flight was online or not."""
+        return self._finishPage.online
+
+    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 reset(self):
+        """Resets the wizard to go back to the login page."""
+        self._initialize()
+
+    def setStage(self, stage):
+        """Set the flight stage to the given one."""
+        if stage==const.STAGE_TAKEOFF:
+            self._takeoffPage.allowForward()
+        elif stage==const.STAGE_LANDING:
+            if not self._arrivalBriefingPage.metarEdited:
+                print "Downloading arrival METAR again"
+                self.gui.webHandler.getMETARs(self._arrivalMETARCallback,
+                                              [self._bookedFlight.arrivalICAO])
+            
+        elif stage==const.STAGE_END:
+            self._landingPage.flightEnded()
+
+    def _initialize(self):
+        """Initialize the wizard."""
+        self._fleet = None
+        self._fleetCallback = None
+        
+        self._loginResult = None
+        self._bookedFlight = None
+        self._departureGate = "-"
+        self._fuelData = 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 via the GUI and call the given callback."""
+        self._fleetCallback = callback
+        self.gui.getFleet(callback = self._fleetRetrieved, force = force)
+
+    def _fleetRetrieved(self, fleet):
+        """Callback for the fleet retrieval."""
+        self._fleet = fleet
+        if self._fleetCallback is not None:
+            self._fleetCallback(fleet)
+        self._fleetCallback = None
+        
+    def updatePlane(self, callback, tailNumber, status, gateNumber = None):
+        """Update the given plane's gate information."""
+        self.gui.updatePlane(tailNumber, status, gateNumber = gateNumber,
+                             callback = callback)
+
+    def _connectSimulator(self):
+        """Connect to the simulator."""
+        self.gui.connectSimulator(self._bookedFlight.aircraftType)
+
+    def _arrivalMETARCallback(self, returned, result):
+        """Called when the METAR of the arrival airport is retrieved."""
+        gobject.idle_add(self._handleArrivalMETAR, returned, result)
+    
+    def _handleArrivalMETAR(self, returned, result):
+        """Called when the METAR of the arrival airport is retrieved."""
+        icao = self._bookedFlight.arrivalICAO
+        if returned and icao in result.metars:
+            metar = result.metars[icao]
+            if metar!="":
+                self._arrivalBriefingPage.setMETAR(metar)
+    
+#-----------------------------------------------------------------------------
+
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 154)
+++ 	(revision )
@@ -1,2646 +1,0 @@
-# The flight handling "wizard"
-
-from mlx.gui.common import *
-
-import mlx.const as const
-import mlx.fs as fs
-import mlx.acft as acft
-from mlx.checks import PayloadChecker
-import mlx.util as util
-from mlx.pirep import PIREP
-from mlx.i18n import xstr
-
-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, completedHelp = None):
-        """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)
-
-        mainBox = gtk.VBox()        
-
-        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(mainBox)
-        self._vbox.pack_start(alignment, True, True, 0)
-        
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.set_padding(padding_top = 0, padding_bottom = 16,
-                              padding_left = 0, padding_right = 0)
-
-        self._help = help
-        self._completedHelp = completedHelp
-
-        if self._completedHelp is None or \
-           len(help.splitlines())>=len(completedHelp.splitlines()):
-            longerHelp = help
-        else:
-            longerHelp = completedHelp
-        
-        self._helpLabel = gtk.Label(completedHelp)
-        # FIXME: should be a constant in common
-        self._helpLabel.set_justify(gtk.Justification.CENTER if pygobject
-                                    else gtk.JUSTIFY_CENTER)
-        self._helpLabel.set_use_markup(True)
-        alignment.add(self._helpLabel)
-        mainBox.pack_start(alignment, False, False, 0)
-
-        self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                            xscale = 1.0, yscale = 1.0)
-        mainBox.pack_start(self._mainAlignment, True, True, 0)
-                                            
-        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._completed = 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, sensitive = True,
-                  tooltip = None, clicked = None):
-        """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
-        button.set_sensitive(sensitive)
-        if tooltip is not None:
-            button.set_tooltip_text(tooltip)
-        if clicked is not None:
-            button.connect("clicked", clicked)
-        return button
-
-    def addPreviousButton(self, sensitive = True, clicked = None):
-        """Add the 'Next' button to the page."""
-        return self.addButton(xstr("button_previous"),
-                              sensitive = sensitive,
-                              tooltip = xstr("button_previous_tooltip"),
-                              clicked = clicked)
-
-    def addNextButton(self, default = True, sensitive = True,
-                      clicked = None):
-        """Add the 'Next' button to the page."""
-        return self.addButton(xstr("button_next"),
-                              default = default,
-                              sensitive = sensitive,
-                              tooltip = xstr("button_next_tooltip"),
-                              clicked = clicked)
-
-    def initialize(self):
-        """Initialize the page.
-
-        It sets up the primary help, and calls the activate() function."""
-        self._helpLabel.set_markup(self._help)
-        self._helpLabel.set_sensitive(True)
-        self.activate()
-
-    def activate(self):
-        """Called when this page becomes active.
-
-        This default implementation does nothing."""
-        pass
-
-    def complete(self):
-        """Called when the page is completed.
-
-        It greys out/changes the help text and then calls finalize()."""
-        self.finalize()
-        if self._completedHelp is None:
-            self._helpLabel.set_sensitive(False)
-        else:
-            self._helpLabel.set_markup(self._completedHelp)
-        self._completed = True
-
-    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._completed = 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."""
-        super(LoginPage, self).__init__(wizard, xstr("login"),
-                                        xstr("loginHelp"))
-
-        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, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        label = gtk.Label(xstr("label_pilotID"))
-        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(xstr("login_pilotID_tooltip"))
-        table.attach(self._pilotID, 1, 2, 0, 1)
-        label.set_mnemonic_widget(self._pilotID)
-
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        label = gtk.Label(xstr("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(xstr("login_password_tooltip"))
-        table.attach(self._password, 1, 2, 1, 2)
-        label.set_mnemonic_widget(self._password)
-
-        self._rememberButton = gtk.CheckButton(xstr("remember_password"))
-        self._rememberButton.set_use_underline(True)
-        self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
-        table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
-
-        self._loginButton = self.addButton(xstr("button_login"), default = True)
-        self._loginButton.set_sensitive(False)
-        self._loginButton.connect("clicked", self._loginClicked)
-        self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))        
-
-    def activate(self):
-        """Activate the page."""
-        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(xstr("login_busy"))
-        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(parent = self._wizard.gui.mainWindow,
-                                           type = MESSAGETYPE_ERROR,
-                                           message_format = xstr("login_invalid"))
-                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-                dialog.set_title(WINDOW_TITLE_BASE)
-                dialog.format_secondary_markup(xstr("login_invalid_sec"))
-                dialog.run()
-                dialog.hide()
-        else:
-            dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
-                                       type = MESSAGETYPE_ERROR,
-                                       message_format = xstr("login_failconn"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            dialog.format_secondary_markup(xstr("login_failconn_sec"))
-            
-            dialog.run()
-            dialog.hide()
-
-#-----------------------------------------------------------------------------
-
-class FlightSelectionPage(Page):
-    """The page to select the flight."""
-    def __init__(self, wizard):
-        """Construct the flight selection page."""
-        help = xstr("flightsel_help") 
-        completedHelp = xstr("flightsel_chelp")
-        super(FlightSelectionPage, self).__init__(wizard, xstr("flightsel_title"),
-                                                  help, completedHelp = completedHelp)
-
-
-        self._listStore = gtk.ListStore(str, str, str, str)
-        self._flightList = gtk.TreeView(self._listStore)
-        column = gtk.TreeViewColumn(xstr("flightsel_no"), gtk.CellRendererText(),
-                                    text = 1)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_deptime"), gtk.CellRendererText(),
-                                    text = 0)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_from"), gtk.CellRendererText(),
-                                    text = 2)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_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)
-        # FIXME: these should be constants in common.py
-        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.addNextButton(sensitive = False,
-                                          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._completed:
-            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._wizard.gui.enableFlightInfo()
-
-            self._updateDepartureGate()
-        
-    def _updateDepartureGate(self):
-        """Update the departure gate for the booked flight."""
-        flight = self._wizard._bookedFlight
-        if self._wizard.gui.config.onlineGateSystem:
-            if flight.departureICAO=="LHBP":
-                self._wizard.getFleet(self._fleetRetrieved)
-            else:
-                self._wizard.updatePlane(self._planeUpdated,
-                                         flight.tailNumber,
-                                         const.PLANE_AWAY)
-        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()
-
-    def _planeUpdated(self, success):
-        """Callback for the plane updating."""
-        self._nextDistance = 2
-        self._wizard.jumpPage(2)
-        
-#-----------------------------------------------------------------------------
-
-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."""
-        super(GateSelectionPage, self).__init__(wizard, xstr("gatesel_title"),
-                                                xstr("gatesel_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)        
-
-        self.addPreviousButton(clicked = self._backClicked)
-        
-        self._button = self.addNextButton(sensitive = False,
-                                          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._completed:
-            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)
-        else:
-            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(parent = self._wizard.gui.mainWindow,
-                                       type = MESSAGETYPE_ERROR,
-                                       message_format = xstr("gatesel_conflict"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            dialog.format_secondary_markup(xstr("gatesel_conflict_sec"))
-            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."
-        completedHelp = "The basic data of your flight can be read below."
-        super(ConnectPage, self).__init__(wizard, xstr("connect_title"),
-                                          xstr("connect_help"),
-                                          completedHelp = xstr("connect_chelp"))
-        
-        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(xstr("connect_flightno"))
-        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(7)
-        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(xstr("connect_acft"))
-        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(xstr("connect_tailno"))
-        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(10)
-        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(xstr("connect_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(6)
-        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(xstr("connect_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)
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addButton(xstr("button_connect"), default = True,
-                                      tooltip = xstr("button_connect_tooltip"))
-        self._clickedID = self._button.connect("clicked", self._connectClicked)
-
-    def activate(self):
-        """Setup the departure information."""
-        self._button.set_label(xstr("button_connect"))
-        self._button.set_use_underline(True)
-        self._button.set_tooltip_text(xstr("button_connect_tooltip"))
-        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(xstr("button_next"))
-        self._button.set_use_underline(True)
-        self._button.set_tooltip_text(xstr("button_next_tooltip"))
-        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."""
-        super(PayloadPage, self).__init__(wizard, xstr("payload_title"),
-                                          xstr("payload_help"),
-                                          completedHelp = xstr("payload_chelp"))
-
-        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(xstr("payload_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(xstr("payload_pax"))
-        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(xstr("payload_bag"))
-        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(xstr("payload_cargo"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 3, 4)
-
-        self._cargoWeight = IntegerEntry(defaultValue = 0)
-        self._cargoWeight.set_width_chars(6)
-        self._cargoWeight.connect("integer-changed", self._cargoWeightChanged)
-        self._cargoWeight.set_tooltip_text(xstr("payload_cargo_tooltip"))
-        table.attach(self._cargoWeight, 1, 2, 3, 4)
-        label.set_mnemonic_widget(self._cargoWeight)
-
-        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
-        
-        label = gtk.Label(xstr("payload_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>" + xstr("payload_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(xstr("payload_fszfw"))
-        self._zfwButton.set_use_underline(True)
-        self._zfwButton.connect("clicked", self._zfwRequested)
-        self._zfwButton.set_tooltip_text(xstr("payload_fszfw_tooltip"))
-        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.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight entered."""
-        return self._cargoWeight.get_int()
-
-    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._cargoWeight.set_int(bookedFlight.cargoWeight)
-        self._cargoWeight.set_sensitive(True)
-        self._mailWeight.set_text(str(bookedFlight.mailWeight))
-        self._simulatorZFW.set_text("-")
-        self._simulatorZFWValue = None
-        self._zfwButton.set_sensitive(True)
-        self._updateCalculatedZFW()
-
-    def finalize(self):
-        """Finalize the payload page."""
-        self._cargoWeight.set_sensitive(False)
-        self._wizard.gui.initializeWeightHelp()
-
-    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._cargoWeight.get_int()
-        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, weight):
-        """Called when the cargo weight has changed."""
-        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(xstr("payload_zfw_busy"))
-        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."""
-        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):
-        super(TimePage, self).__init__(wizard, xstr("time_title"),
-                                       xstr("time_help"),
-                                       completedHelp = xstr("time_chelp"))
-
-        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(xstr("time_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(xstr("time_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(xstr("time_fs"))
-        self._timeButton.set_use_underline(True)
-        self._timeButton.set_tooltip_text(xstr("time_fs_tooltip"))
-        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.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(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()))
-        self._simulatorTime.set_text("-")
-
-    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(xstr("time_busy"))
-        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."""
-        if not self._completed:
-            gui = self._wizard.gui
-            gui.beginBusy(xstr("fuel_get_busy"))
-            
-            gui.simulator.getFuel(gui.flight.aircraft.fuelTanks,
-                                  self._handleFuel)
-        else:
-            self._wizard.nextPage()
-
-    def _handleFuel(self, fuelData):
-        """Callback for the fuel query operation."""
-        gobject.idle_add(self._processFuel, fuelData)
-
-    def _processFuel(self, fuelData):
-        """Process the given fuel data."""
-        self._wizard.gui.endBusy()
-        self._wizard._fuelData = fuelData
-        self._wizard.nextPage()
-        
-#-----------------------------------------------------------------------------
-
-class FuelTank(gtk.VBox):
-    """Widget for the fuel tank."""
-    def __init__(self, fuelTank, name, capacity, currentWeight):
-        """Construct the widget for the tank with the given name."""
-        super(FuelTank, self).__init__()
-
-        self._enabled = True
-        self.fuelTank = fuelTank
-        self.capacity = capacity
-        self.currentWeight = currentWeight
-        self.expectedWeight = currentWeight
-
-        label = gtk.Label("<b>" + name + "</b>")
-        label.set_use_markup(True)
-        label.set_use_underline(True)
-        label.set_justify(JUSTIFY_CENTER)
-        label.set_alignment(0.5, 1.0)
-        self.pack_start(label, False, False, 4)
-
-        self._tankFigure = gtk.EventBox()
-        self._tankFigure.set_size_request(38, -1)
-        self._tankFigure.set_visible_window(False)
-        self._tankFigure.set_tooltip_markup(xstr("fuel_tank_tooltip"))
-
-        if pygobject:
-            self._tankFigure.connect("draw", self._drawTankFigure)
-        else:
-            self._tankFigure.connect("expose_event", self._drawTankFigure)
-        self._tankFigure.connect("button_press_event", self._buttonPressed)
-        self._tankFigure.connect("motion_notify_event", self._motionNotify)
-        self._tankFigure.connect("scroll-event", self._scrolled)
-        
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 1.0)
-        alignment.add(self._tankFigure)
-
-        self.pack_start(alignment, True, True, 4)
-
-        self._expectedButton = gtk.SpinButton()
-        self._expectedButton.set_numeric(True)
-        self._expectedButton.set_range(0, self.capacity)
-        self._expectedButton.set_increments(10, 100)
-        self._expectedButton.set_value(currentWeight)
-        self._expectedButton.set_alignment(1.0)
-        self._expectedButton.set_width_chars(5)
-        self._expectedButton.connect("value-changed", self._expectedChanged)
-
-        label.set_mnemonic_widget(self._expectedButton)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 1.0)
-        alignment.add(self._expectedButton) 
-        self.pack_start(alignment, False, False, 4)
-
-    def setCurrent(self, currentWeight):
-        """Set the current weight."""
-        self.currentWeight = currentWeight
-        self._redraw()
-
-    def isCorrect(self):
-        """Determine if the contents of the fuel tank are as expected"""
-        return abs(self.expectedWeight - self.currentWeight)<=1
-
-    def disable(self):
-        """Disable the fuel tank."""
-        self._expectedButton.set_sensitive(False)
-        self._enabled = False
-
-    def _redraw(self):
-        """Redraw the tank figure."""
-        self._tankFigure.queue_draw()
-
-    def _drawTankFigure(self, tankFigure, eventOrContext):
-        """Draw the tank figure."""
-        triangleSize = 5
-
-        context = eventOrContext if pygobject else tankFigure.window.cairo_create()
-        (xOffset, yOffset) = (0, 0) if pygobject \
-                             else (tankFigure.allocation.x, tankFigure.allocation.y)
-        
-        width = tankFigure.get_allocated_width() if pygobject \
-                else tankFigure.allocation.width
-        height = tankFigure.get_allocated_height() if pygobject \
-                 else tankFigure.allocation.height
-
-        rectangleX0 = triangleSize
-        rectangleY0 = triangleSize
-        rectangleX1 = width - 1 - triangleSize
-        rectangleY1 = height - 1 - triangleSize
-        rectangleLineWidth = 2.0
-
-        context.set_source_rgb(0.0, 0.0, 0.0)
-        context.set_line_width(rectangleLineWidth)
-        context.rectangle(xOffset + rectangleX0 + rectangleLineWidth/2,
-                          yOffset + rectangleY0 + rectangleLineWidth/2,
-                          rectangleX1 - rectangleX0 - rectangleLineWidth,
-                          rectangleY1 - rectangleY0 - rectangleLineWidth)
-        context.stroke()
-
-        rectangleInnerLeft   = rectangleX0 + rectangleLineWidth
-        rectangleInnerRight  = rectangleX1 - rectangleLineWidth
-        self._rectangleInnerTop = rectangleInnerTop = rectangleY0 + rectangleLineWidth
-        self._rectangleInnerBottom = rectangleInnerBottom = rectangleY1 - rectangleLineWidth
-
-        rectangleInnerWidth = rectangleInnerRight - rectangleInnerLeft
-        rectangleInnerHeight = rectangleInnerBottom - rectangleInnerTop
-
-        context.set_source_rgb(1.0, 0.9, 0.6)
-        currentHeight = self.currentWeight * rectangleInnerHeight / self.capacity
-        currentX = rectangleInnerTop + rectangleInnerHeight - currentHeight
-        context.rectangle(xOffset + rectangleInnerLeft,
-                          yOffset + rectangleInnerTop +
-                          rectangleInnerHeight - currentHeight,
-                          rectangleInnerWidth, currentHeight)
-        context.fill()
-
-        expectedHeight = self.expectedWeight * rectangleInnerHeight / self.capacity
-        expectedY = rectangleInnerTop + rectangleInnerHeight - expectedHeight
-
-        context.set_line_width(1.5)
-        context.set_source_rgb(0.0, 0.85, 0.85)
-        context.move_to(xOffset + rectangleX0, yOffset + expectedY)
-        context.line_to(xOffset + rectangleX1, yOffset + expectedY)
-        context.stroke()
-
-        context.set_line_width(0.0)
-        context.move_to(xOffset + 0, yOffset + expectedY - triangleSize)
-        context.line_to(xOffset + 0, yOffset + expectedY + triangleSize)
-        context.line_to(xOffset + rectangleX0 + 1, yOffset + expectedY)
-        context.line_to(xOffset + 0, yOffset + expectedY - triangleSize)
-        context.fill()
-
-        context.set_line_width(0.0)
-        context.move_to(xOffset + width, yOffset + expectedY - triangleSize)
-        context.line_to(xOffset + width, yOffset + expectedY + triangleSize)
-        context.line_to(xOffset + rectangleX1 - 1, yOffset + expectedY)
-        context.line_to(xOffset + width, yOffset + expectedY - triangleSize)
-        context.fill()
-
-        return True
-
-    def _setExpectedFromY(self, y):
-        """Set the expected weight from the given Y-coordinate."""
-        level = (self._rectangleInnerBottom - y) / \
-                (self._rectangleInnerBottom - self._rectangleInnerTop)
-        level = min(1.0, max(0.0, level))
-        self._expectedButton.set_value(level * self.capacity)
-        
-    def _buttonPressed(self, tankFigure, event):
-        """Called when a button is pressed in the figure.
-
-        The expected level will be set there."""
-        if self._enabled and event.button==1:
-            self._setExpectedFromY(event.y)
-        
-    def _motionNotify(self, tankFigure, event):
-        """Called when the mouse pointer moves within the area of a tank figure."""
-        if self._enabled and event.state==BUTTON1_MASK:            
-            self._setExpectedFromY(event.y)
-
-    def _scrolled(self, tankFigure, event):
-        """Called when a scroll event is received."""
-        if self._enabled:
-            increment = 1 if event.state==CONTROL_MASK \
-                        else 100 if event.state==SHIFT_MASK \
-                        else 10 if event.state==0 else 0
-            if increment!=0:
-                if event.direction==SCROLL_DOWN:
-                    increment *= -1
-                self._expectedButton.spin(SPIN_USER_DEFINED, increment)
-        
-    def _expectedChanged(self, spinButton):
-        """Called when the expected value has changed."""
-        self.expectedWeight = spinButton.get_value_as_int()
-        self._redraw()        
-
-#-----------------------------------------------------------------------------
-
-class FuelPage(Page):
-    """The page containing the fuel tank filling."""
-    _pumpStep = 0.02
-    
-    def __init__(self, wizard):
-        """Construct the page."""
-        super(FuelPage, self).__init__(wizard, xstr("fuel_title"),
-                                       xstr("fuel_help"),
-                                       completedHelp = xstr("fuel_chelp"))
-
-        self._fuelTanks = []
-        self._fuelTable = None
-        self._fuelAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                            xscale = 0.0, yscale = 1.0)
-        self.setMainWidget(self._fuelAlignment)
-
-        tanks = acft.MostFuelTankAircraft.fuelTanks
-        tankData = ((2500, 3900),) * len(tanks)
-        self._setupTanks(tanks, tankData)
-
-        self._backButton = self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-        self._pumpIndex = 0
-
-    def activate(self):
-        """Activate the page."""
-        gui = self._wizard.gui
-
-        self._setupTanks(gui.flight.aircraft.fuelTanks,
-                         self._wizard._fuelData)
-
-    def finalize(self):
-        """Finalize the page."""
-        for fuelTank in self._fuelTanks:
-            fuelTank.disable()
-
-    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._completed:
-            self._pumpIndex = 0
-            self._wizard.gui.beginBusy(xstr("fuel_pump_busy"))
-            self._pump()
-        else:
-            self._wizard.nextPage()        
-
-    def _setupTanks(self, tanks, tankData):
-        """Setup the tanks for the given data."""
-        numTanks = len(tanks)
-        if self._fuelTable is not None:
-            self._fuelAlignment.remove(self._fuelTable)
-
-        self._fuelTanks = []
-        self._fuelTable = gtk.Table(numTanks, 1)
-        self._fuelTable.set_col_spacings(16)
-        for i in range(0, numTanks):
-            tank = tanks[i]
-            (current, capacity) = tankData[i]
-
-            fuelTank = FuelTank(tank,
-                                xstr("fuel_tank_" +
-                                     const.fuelTank2string(tank)),
-                                capacity, current)
-            self._fuelTable.attach(fuelTank, i, i+1, 0, 1)
-            self._fuelTanks.append(fuelTank)
-            
-        self._fuelAlignment.add(self._fuelTable)
-        self.show_all()
-
-    def _pump(self):
-        """Perform one step of pumping.
-
-        It is checked, if the current tank's contents are of the right
-        quantity. If not, it is filled one step further to the desired
-        contents. Otherwise the next tank is started. If all tanks are are
-        filled, the next page is selected."""
-        numTanks = len(self._fuelTanks)
-
-        fuelTank = None
-        while self._pumpIndex < numTanks:
-            fuelTank = self._fuelTanks[self._pumpIndex]
-            if fuelTank.isCorrect():
-                self._pumpIndex += 1
-                fuelTank = None
-            else:
-                break
-
-        if fuelTank is None:
-            self._wizard.gui.endBusy()
-            self._wizard.nextPage()
-        else:
-            currentLevel = fuelTank.currentWeight / fuelTank.capacity
-            expectedLevel = fuelTank.expectedWeight / fuelTank.capacity
-            if currentLevel<expectedLevel:
-                currentLevel += FuelPage._pumpStep
-                if currentLevel>expectedLevel: currentLevel = expectedLevel
-            else:
-                currentLevel -= FuelPage._pumpStep
-                if currentLevel<expectedLevel: currentLevel = expectedLevel
-            fuelTank.setCurrent(currentLevel * fuelTank.capacity)
-            self._wizard.gui.simulator.setFuelLevel([(fuelTank.fuelTank,
-                                                      currentLevel)])
-            gobject.timeout_add(50, self._pump)
-        
-#-----------------------------------------------------------------------------
-
-class RoutePage(Page):
-    """The page containing the route and the flight level."""
-    def __init__(self, wizard):
-        """Construct the page."""
-        super(RoutePage, self).__init__(wizard, xstr("route_title"),
-                                        xstr("route_help"),
-                                        completedHelp = xstr("route_chelp"))
-
-        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(xstr("route_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_tooltip_text(xstr("route_level_tooltip"))
-        self._cruiseLevel.set_numeric(True)
-        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
-        label.set_mnemonic_widget(self._cruiseLevel)
-        self._filedCruiseLevel = 240
-
-        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(xstr("route_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._uppercasingRoute = False
-
-        self._route = gtk.TextView()
-        self._route.set_tooltip_text(xstr("route_route_tooltip"))
-        self._route.set_wrap_mode(WRAP_WORD)
-        self._route.get_buffer().connect("changed", self._routeChanged)
-        self._route.get_buffer().connect_after("insert-text", self._routeInserted)
-        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.addPreviousButton(clicked = self._backClicked)        
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def filedCruiseLevel(self):
-        """Get the filed cruise level."""
-        return self._filedCruiseLevel
-
-    @property
-    def cruiseLevel(self):
-        """Get the cruise level."""
-        return self._cruiseLevel.get_value_as_int()
-
-    @property
-    def route(self):
-        """Get the route."""
-        return self._getRoute()
-
-    def activate(self):
-        """Setup the route from the booked flight."""
-        self._cruiseLevel.set_value(240)
-        self._filedCruiseLevel = 240
-        self._route.get_buffer().set_text(self._wizard._bookedFlight.route)
-        self._updateForwardButton()
-
-    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."""
-        if not self._uppercasingRoute:
-            self._updateForwardButton()
-
-    def _routeInserted(self, textBuffer, iter, text, length):
-        """Called when new characters are inserted into the route.
-
-        It uppercases all characters."""
-        if not self._uppercasingRoute:
-            self._uppercasingRoute = True
-
-            iter1 = iter.copy()
-            iter1.backward_chars(length)
-            textBuffer.delete(iter, iter1)
-
-            textBuffer.insert(iter, text.upper())
-
-            self._uppercasingRoute = False
-
-    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._completed:
-            self._wizard.nextPage()
-        else:
-            bookedFlight = self._wizard._bookedFlight
-            self._filedCruiseLevel = self.cruiseLevel
-            self._wizard.gui.beginBusy(xstr("route_down_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(xstr("route_down_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 = xstr("briefing_title") % (1 if departure else 2,
-                                          xstr("briefing_departure")
-                                          if departure
-                                          else xstr("briefing_arrival"))
-        super(BriefingPage, self).__init__(wizard, title, xstr("briefing_help"),
-                                           completedHelp = xstr("briefing_chelp"))
-
-        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(xstr("briefing_notams_init"))
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.set_size_request(-1, 128)
-        # FIXME: these constants should be in common
-        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(xstr("briefing_metar_init"))
-        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._uppercasingMETAR = False
-
-        self._metar = gtk.TextView()
-        self._metar.set_accepts_tab(False)
-        self._metar.set_wrap_mode(gtk.WrapMode.WORD if pygobject else gtk.WRAP_WORD)
-        self._metar.get_buffer().connect("changed", self._metarChanged)
-        self._metar.get_buffer().connect_after("insert-text", self._metarInserted)
-        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)
-        self.metarEdited = False
-
-        self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def metar(self):
-        """Get the METAR on the page."""
-        buffer = self._metar.get_buffer()
-        return buffer.get_text(buffer.get_start_iter(),
-                               buffer.get_end_iter(), True)        
-
-    def setMETAR(self, metar):
-        """Set the metar."""
-        self._metar.get_buffer().set_text(metar)
-        self.metarEdited = False
-
-    def activate(self):
-        """Activate the page."""
-        if not self._departure:
-            self._button.set_label(xstr("briefing_button"))
-            self._button.set_has_tooltip(False)
-            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(xstr("briefing_notams_template") % (icao,))
-        buffer = self._notams.get_buffer()
-        if notams is None:
-            buffer.set_text(xstr("briefing_notams_failed"))
-        elif not notams:
-            buffer.set_text(xstr("briefing_notams_missing"))
-        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(xstr("briefing_metar_template") % (icao,))
-        buffer = self._metar.get_buffer()
-        if metar is None:
-            buffer.set_text(xstr("briefing_metar_failed"))
-        else:
-            buffer.set_text(metar)
-
-        label = self._metarFrame.get_label_widget()
-        label.set_use_underline(True)
-        label.set_mnemonic_widget(self._metar)
-
-        self.metarEdited = False
-
-    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._completed:
-                self._wizard.gui.startMonitoring()
-                self._button.set_label(xstr("button_next"))
-                self._button.set_tooltip_text(xstr("button_next_tooltip"))
-                self.complete()
-
-        self._wizard.nextPage()
-
-    def _metarChanged(self, buffer):
-        """Called when the METAR has changed."""
-        if not self._uppercasingMETAR:
-            self.metarEdited = True
-            self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
-                                                       buffer.get_end_iter(),
-                                                       True)!="")
-
-    def _metarInserted(self, textBuffer, iter, text, length):
-        """Called when new characters are inserted into the METAR.
-
-        It uppercases all characters."""
-        if not self._uppercasingMETAR:
-            self._uppercasingMETAR = True
-
-            iter1 = iter.copy()
-            iter1.backward_chars(length)
-            textBuffer.delete(iter, iter1)
-
-            textBuffer.insert(iter, text.upper())
-
-            self._uppercasingMETAR = False
-
-#-----------------------------------------------------------------------------
-
-class TakeoffPage(Page):
-    """Page for entering the takeoff data."""
-    def __init__(self, wizard):
-        """Construct the takeoff page."""
-        super(TakeoffPage, self).__init__(wizard, xstr("takeoff_title"),
-                                          xstr("takeoff_help"),
-                                          completedHelp = xstr("takeoff_chelp"))
-
-        self._forwardAllowed = False
-
-        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)
-
-        label = gtk.Label(xstr("takeoff_runway"))
-        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(xstr("takeoff_runway_tooltip"))
-        self._runway.connect("changed", self._upperChanged)
-        table.attach(self._runway, 1, 3, 0, 1)
-        label.set_mnemonic_widget(self._runway)
-        
-        label = gtk.Label(xstr("takeoff_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(xstr("takeoff_sid_tooltip"))
-        self._sid.connect("changed", self._upperChanged)
-        table.attach(self._sid, 1, 3, 1, 2)
-        label.set_mnemonic_widget(self._sid)
-        
-        label = gtk.Label(xstr("takeoff_v1"))
-        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 = IntegerEntry()
-        self._v1.set_width_chars(4)
-        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip"))
-        self._v1.connect("integer-changed", self._valueChanged)
-        table.attach(self._v1, 2, 3, 2, 3)
-        label.set_mnemonic_widget(self._v1)
-        
-        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 2, 3)
-        
-        label = gtk.Label(xstr("takeoff_vr"))
-        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 = IntegerEntry()
-        self._vr.set_width_chars(4)
-        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip"))
-        self._vr.connect("integer-changed", self._valueChanged)
-        table.attach(self._vr, 2, 3, 3, 4)
-        label.set_mnemonic_widget(self._vr)
-        
-        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 3, 4)
-        
-        label = gtk.Label(xstr("takeoff_v2"))
-        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 = IntegerEntry()
-        self._v2.set_width_chars(4)
-        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip"))
-        self._v2.connect("integer-changed", self._valueChanged)
-        table.attach(self._v2, 2, 3, 4, 5)
-        label.set_mnemonic_widget(self._v2)
-        
-        table.attach(gtk.Label(xstr("label_knots")), 3, 4, 4, 5)
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def runway(self):
-        """Get the runway."""
-        return self._runway.get_text()
-
-    @property
-    def sid(self):
-        """Get the SID."""
-        return self._sid.get_text()
-
-    @property
-    def v1(self):
-        """Get the v1 speed."""
-        return self._v1.get_int()
-
-    @property
-    def vr(self):
-        """Get the vr speed."""
-        return self._vr.get_int()
-
-    @property
-    def v2(self):
-        """Get the v2 speed."""
-        return self._v2.get_int()
-
-    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_int(None)
-        self._v1.set_sensitive(True)
-        self._vr.set_int(None)
-        self._vr.set_sensitive(True)
-        self._v2.set_int(None)
-        self._v2.set_sensitive(True)
-        self._button.set_sensitive(False)
-        
-    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)
-        self._wizard.gui.flight.aircraft.updateV1R2()
-
-    def allowForward(self):
-        """Allow going to the next page."""
-        self._forwardAllowed = True
-        self._updateForwardButton()
-
-    def _updateForwardButton(self):
-        """Update the sensitivity of the forward button based on some conditions."""
-        sensitive = self._forwardAllowed and \
-                    self._runway.get_text()!="" and \
-                    self._sid.get_text()!="" and \
-                    self.v1 is not None and \
-                    self.vr is not None and \
-                    self.v2 is not None and \
-                    self.v1 <= self.vr and \
-                    self.vr <= self.v2
-        self._button.set_sensitive(sensitive)
-
-    def _valueChanged(self, widget, arg = None):
-        """Called when the value of some widget has changed."""
-        self._updateForwardButton()
-        
-    def _upperChanged(self, entry, arg = None):
-        """Called when the value of some entry widget has changed and the value
-        should be converted to uppercase."""
-        entry.set_text(entry.get_text().upper())
-        self._valueChanged(entry, arg)
-        
-    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."""
-        super(LandingPage, self).__init__(wizard, xstr("landing_title"),
-                                          xstr("landing_help"),
-                                          completedHelp = xstr("landing_chelp"))
-
-        self._flightEnded = False
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(5, 5)
-        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(xstr("landing_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(xstr("landing_star_tooltip"))
-        self._star.connect("changed", self._upperChanged)
-        self._star.set_sensitive(False)
-        table.attach(self._star, 2, 4, 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(xstr("landing_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(xstr("landing_transition_tooltip"))
-        self._transition.connect("changed", self._upperChanged)
-        self._transition.set_sensitive(False)
-        table.attach(self._transition, 2, 4, 1, 2)
-        label.set_mnemonic_widget(self._transitionButton)
-
-        label = gtk.Label(xstr("landing_runway"))
-        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(xstr("landing_runway_tooltip"))
-        self._runway.connect("changed", self._upperChanged)
-        table.attach(self._runway, 2, 4, 2, 3)
-        label.set_mnemonic_widget(self._runway)
-
-        label = gtk.Label(xstr("landing_approach"))
-        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(xstr("landing_approach_tooltip"))
-        self._approachType.connect("changed", self._upperChanged)
-        table.attach(self._approachType, 2, 4, 3, 4)
-        label.set_mnemonic_widget(self._approachType)
-
-        label = gtk.Label(xstr("landing_vref"))
-        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 = IntegerEntry()
-        self._vref.set_width_chars(5)
-        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip"))
-        self._vref.connect("integer-changed", self._vrefChanged)
-        table.attach(self._vref, 3, 4, 5, 6)
-        label.set_mnemonic_widget(self._vref)
-        
-        table.attach(gtk.Label(xstr("label_knots")), 4, 5, 5, 6)
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-        # These are needed for correct size calculations
-        self._starButton.set_active(True)
-        self._transitionButton.set_active(True)
-
-    @property
-    def star(self):
-        """Get the STAR or None if none entered."""
-        return self._star.get_text() if self._starButton.get_active() else None
-
-    @property
-    def transition(self):
-        """Get the transition or None if none entered."""
-        return self._transition.get_text() \
-               if self._transitionButton.get_active() else None
-
-    @property
-    def approachType(self):
-        """Get the approach type."""
-        return self._approachType.get_text()
-
-    @property
-    def runway(self):
-        """Get the runway."""
-        return self._runway.get_text()
-
-    @property
-    def vref(self):
-        """Return the landing reference speed."""
-        return self._vref.get_int()
-
-    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_int(None)
-        self._vref.set_sensitive(True)
-
-        self._updateForwardButton()
-
-    def flightEnded(self):
-        """Called when the flight has ended."""
-        self._flightEnded = 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)
-        self._wizard.gui.flight.aircraft.updateVRef()
-        # FIXME: Perhaps a separate initialize() call which would set up
-        # defaults? -> use reset()
-        self._flightEnded = 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):
-        """Update the sensitivity of the forward button."""
-        sensitive = self._flightEnded and \
-                    (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()!="" and \
-                    self.vref is not None
-        self._button.set_sensitive(sensitive)
-
-    def _upperChanged(self, entry):
-        """Called for entry widgets that must be converted to uppercase."""
-        entry.set_text(entry.get_text().upper())
-        self._updateForwardButton()
-
-    def _vrefChanged(self, widget, value):
-        """Called when the Vref 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._wizard.gui.config.onlineGateSystem and \
-           not self._completed and \
-           self._wizard.bookedFlight.arrivalICAO=="LHBP":
-            self._wizard.getFleet(callback = self._fleetRetrieved,
-                                  force = True)
-        else:
-            self._wizard.nextPage()
-
-    def _fleetRetrieved(self, fleet):
-        """Callback for the fleet retrieval."""
-        self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-class FinishPage(Page):
-    """Flight finish page."""
-    _flightTypes = [ ("flighttype_scheduled", const.FLIGHTTYPE_SCHEDULED),
-                     ("flighttype_ot", const.FLIGHTTYPE_OLDTIMER),
-                     ("flighttype_vip", const.FLIGHTTYPE_VIP),
-                     ("flighttype_charter", const.FLIGHTTYPE_CHARTER) ]
-    
-    def __init__(self, wizard):
-        """Construct the finish page."""
-        super(FinishPage, self).__init__(wizard, xstr("finish_title"),
-                                         xstr("finish_help"))
-        
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(8, 2)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_rating"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 0, 1)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._flightRating = gtk.Label()
-        self._flightRating.set_width_chars(8)
-        self._flightRating.set_alignment(0.0, 0.5)
-        self._flightRating.set_use_markup(True)
-        labelAlignment.add(self._flightRating)
-        table.attach(labelAlignment, 1, 2, 0, 1)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_flight_time"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 1, 2)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._flightTime = gtk.Label()
-        self._flightTime.set_width_chars(10)
-        self._flightTime.set_alignment(0.0, 0.5)
-        self._flightTime.set_use_markup(True)
-        labelAlignment.add(self._flightTime)
-        table.attach(labelAlignment, 1, 2, 1, 2)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_block_time"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 2, 3)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._blockTime = gtk.Label()
-        self._blockTime.set_width_chars(10)
-        self._blockTime.set_alignment(0.0, 0.5)
-        self._blockTime.set_use_markup(True)
-        labelAlignment.add(self._blockTime)
-        table.attach(labelAlignment, 1, 2, 2, 3)
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_distance"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 3, 4)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._distanceFlown = gtk.Label()
-        self._distanceFlown.set_width_chars(10)
-        self._distanceFlown.set_alignment(0.0, 0.5)
-        self._distanceFlown.set_use_markup(True)
-        labelAlignment.add(self._distanceFlown)
-        table.attach(labelAlignment, 1, 2, 3, 4)
-        
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_fuel"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 4, 5)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._fuelUsed = gtk.Label()
-        self._fuelUsed.set_width_chars(10)
-        self._fuelUsed.set_alignment(0.0, 0.5)
-        self._fuelUsed.set_use_markup(True)
-        labelAlignment.add(self._fuelUsed)
-        table.attach(labelAlignment, 1, 2, 4, 5)
-
-        labelAlignment = gtk.Alignment(xalign = 1.0, xscale = 0.0,
-                                       yalign = 0.5, yscale = 0.0)
-        label = gtk.Label(xstr("finish_type"))
-        label.set_use_underline(True)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 5, 6)
-
-        flightTypeModel = gtk.ListStore(str, int)
-        for (name, type) in FinishPage._flightTypes:
-            flightTypeModel.append([xstr(name), type])
-
-        self._flightType = gtk.ComboBox(model = flightTypeModel)
-        renderer = gtk.CellRendererText()
-        self._flightType.pack_start(renderer, True)
-        self._flightType.add_attribute(renderer, "text", 0)
-        self._flightType.set_tooltip_text(xstr("finish_type_tooltip"))
-        self._flightType.set_active(0)
-        self._flightType.connect("changed", self._flightTypeChanged)
-        flightTypeAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        flightTypeAlignment.add(self._flightType)
-        table.attach(flightTypeAlignment, 1, 2, 5, 6)
-        label.set_mnemonic_widget(self._flightType)        
-
-        self._onlineFlight = gtk.CheckButton(xstr("finish_online"))
-        self._onlineFlight.set_use_underline(True)
-        self._onlineFlight.set_tooltip_text(xstr("finish_online_tooltip"))
-        onlineFlightAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        onlineFlightAlignment.add(self._onlineFlight)
-        table.attach(onlineFlightAlignment, 1, 2, 6, 7)
-
-        labelAlignment = gtk.Alignment(xalign = 1.0, xscale = 0.0,
-                                       yalign = 0.5, yscale = 0.0)
-        self._gateLabel = gtk.Label(xstr("finish_gate"))
-        self._gateLabel.set_use_underline(True)
-        labelAlignment.add(self._gateLabel)
-        table.attach(labelAlignment, 0, 1, 7, 8)
-
-        self._gatesModel = gtk.ListStore(str)
-
-        self._gate = gtk.ComboBox(model = self._gatesModel)
-        renderer = gtk.CellRendererText()
-        self._gate.pack_start(renderer, True)
-        self._gate.add_attribute(renderer, "text", 0)
-        self._gate.set_tooltip_text(xstr("finish_gate_tooltip"))
-        self._gate.connect("changed", self._gateChanged)               
-        gateAlignment = gtk.Alignment(xalign=0.0, xscale=1.0)
-        gateAlignment.add(self._gate)
-        table.attach(gateAlignment, 1, 2, 7, 8)
-        self._gateLabel.set_mnemonic_widget(self._gate)
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._saveButton = self.addButton(xstr("finish_save"),
-                                          sensitive = False,
-                                          clicked = self._saveClicked,
-                                          tooltip = xstr("finish_save_tooltip"))
-        self._savePIREPDialog = None
-        self._lastSavePath = None
-        
-        self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
-                                          sensitive = False,
-                                          clicked = self._sendClicked,
-                                          tooltip = xstr("sendPIREP_tooltip"))
-        
-    @property
-    def flightType(self):
-        """Get the flight type."""
-        index = self._flightType.get_active()
-        return None if index<0 else self._flightType.get_model()[index][1]
-
-    @property
-    def online(self):
-        """Get whether the flight was an online flight or not."""
-        return self._onlineFlight.get_active()
-
-    def activate(self):
-        """Activate the page."""
-        flight = self._wizard.gui._flight
-        rating = flight.logger.getRating()
-        if rating<0:
-            self._flightRating.set_markup('<b><span foreground="red">NO GO</span></b>')
-        else:
-            self._flightRating.set_markup("<b>%.1f %%</b>" % (rating,))
-
-        flightLength = flight.flightTimeEnd - flight.flightTimeStart
-        self._flightTime.set_markup("<b>%s</b>" % \
-                                    (util.getTimeIntervalString(flightLength),))
-        
-        blockLength = flight.blockTimeEnd - flight.blockTimeStart
-        self._blockTime.set_markup("<b>%s</b>" % \
-                                   (util.getTimeIntervalString(blockLength),))
-
-        self._distanceFlown.set_markup("<b>%.2f NM</b>" % \
-                                       (flight.flownDistance,))
-        
-        self._fuelUsed.set_markup("<b>%.0f kg</b>" % \
-                                  (flight.startFuel - flight.endFuel,))
-
-        self._flightType.set_active(-1)
-        self._onlineFlight.set_active(True)
-
-        self._gatesModel.clear()
-        if self._wizard.gui.config.onlineGateSystem and \
-           self._wizard.bookedFlight.arrivalICAO=="LHBP":
-            occupiedGates = self._wizard._fleet.getOccupiedGateNumbers()
-            for gateNumber in const.lhbpGateNumbers:
-                if gateNumber not in occupiedGates:
-                    self._gatesModel.append([gateNumber])
-            self._gateLabel.set_sensitive(True)
-            self._gate.set_sensitive(True)
-            self._gate.set_active(-1)
-        else:
-            self._gateLabel.set_sensitive(False)
-            self._gate.set_sensitive(False)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _updateButtons(self):
-        """Update the sensitivity state of the buttons."""
-        sensitive = self._flightType.get_active()>=0 and \
-                    (self._gatesModel.get_iter_first() is None or
-                     self._gate.get_active()>=0)
-        
-        self._saveButton.set_sensitive(sensitive)
-        self._sendButton.set_sensitive(sensitive)        
-
-    def _flightTypeChanged(self, comboBox):
-        """Called when the flight type has changed."""
-        self._updateButtons()
-
-    def _gateChanged(self, comboBox):
-        """Called when the arrival gate has changed."""
-        self._updateButtons()
-
-    def _saveClicked(self, button):
-        """Called when the Save PIREP button is clicked."""
-        gui = self._wizard.gui
-
-        bookedFlight = gui.bookedFlight
-        tm = time.gmtime()
-        
-        fileName = "%s %s %02d%02d %s-%s.pirep" % \
-                   (gui.loginResult.pilotID,
-                    str(bookedFlight.departureTime.date()),
-                    tm.tm_hour, tm.tm_min,
-                    bookedFlight.departureICAO,
-                    bookedFlight.arrivalICAO)
-
-        dialog = self._getSaveDialog()
-
-        if self._lastSavePath is None:
-            pirepDirectory = gui.config.pirepDirectory
-            if pirepDirectory is not None:
-                dialog.set_current_folder(pirepDirectory)
-        else:
-            dialog.set_current_folder(os.path.dirname(self._lastSavePath))
-            
-        dialog.set_current_name(fileName)
-        result = dialog.run()
-        dialog.hide()
-
-        if result==RESPONSETYPE_OK:
-            pirep = PIREP(gui)
-
-            self._lastSavePath = dialog.get_filename()
-            
-            if pirep.save(self._lastSavePath):
-                type = MESSAGETYPE_INFO
-                message = xstr("finish_save_done")
-                secondary = None
-            else:
-                type = MESSAGETYPE_ERROR
-                message = xstr("finish_save_failed")
-                secondary = xstr("finish_save_failed_sec")
-                
-            dialog = gtk.MessageDialog(parent = gui.mainWindow,
-                                       type = type, message_format = message)
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            if secondary is not None:
-                dialog.format_secondary_markup(secondary)
-
-            dialog.run()
-            dialog.hide()
-
-    def _getSaveDialog(self):
-        """Get the PIREP saving dialog.
-
-        If it does not exist yet, create it."""
-        if self._savePIREPDialog is None:
-            gui = self._wizard.gui
-            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                           xstr("finish_save_title"),
-                                           action = FILE_CHOOSER_ACTION_SAVE,
-                                           buttons = (gtk.STOCK_CANCEL,
-                                                      RESPONSETYPE_CANCEL,
-                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
-                                           parent = gui.mainWindow)
-            dialog.set_modal(True)
-            dialog.set_do_overwrite_confirmation(True)
-            
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("loadPIREP_filter_pireps"))
-            filter.add_pattern("*.pirep")
-            dialog.add_filter(filter)
-            
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("loadPIREP_filter_all"))
-            filter.add_pattern("*.*")
-            dialog.add_filter(filter)
-
-            self._savePIREPDialog = dialog
-
-        return self._savePIREPDialog
-        
-
-    def _sendClicked(self, button):
-        """Called when the Send button is clicked."""
-        pirep = PIREP(self._wizard.gui)
-        self._wizard.gui.sendPIREP(pirep,
-                                   callback = self._handlePIREPSent)
-
-    def _handlePIREPSent(self, returned, result):
-        """Callback for the PIREP sending result."""
-        if self._wizard.gui.config.onlineGateSystem and returned and result.success:
-            bookedFlight = self._wizard.bookedFlight
-            if bookedFlight.arrivalICAO=="LHBP":
-                iter = self._gate.get_active_iter()                
-                gateNumber = None if iter is None \
-                             else self._gatesModel.get_value(iter, 0)
-                
-                status = const.PLANE_PARKING if gateNumber is None \
-                         else const.PLANE_HOME
-            else:
-                gateNumber = None
-                status = const.PLANE_AWAY
-
-            self._wizard.updatePlane(self._planeUpdated,
-                                     bookedFlight.tailNumber,
-                                     status, gateNumber = gateNumber)
-
-    def _planeUpdated(self, success):
-        """Callback for the plane updating."""
-        pass
-        
-#-----------------------------------------------------------------------------
-
-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._payloadPage = PayloadPage(self) 
-        self._pages.append(self._payloadPage)
-        self._payloadIndex = len(self._pages)
-        self._pages.append(TimePage(self))
-        self._pages.append(FuelPage(self))
-        self._routePage = RoutePage(self)
-        self._pages.append(self._routePage)
-        self._departureBriefingPage = BriefingPage(self, True)
-        self._pages.append(self._departureBriefingPage)
-        self._arrivalBriefingPage = BriefingPage(self, False)
-        self._pages.append(self._arrivalBriefingPage)
-        self._arrivalBriefingIndex = len(self._pages)
-        self._takeoffPage = TakeoffPage(self) 
-        self._pages.append(self._takeoffPage)
-        self._landingPage = LandingPage(self) 
-        self._pages.append(self._landingPage)
-        self._finishPage = FinishPage(self)
-        self._pages.append(self._finishPage)
-        
-        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._completed:
-                page.complete()
-            self.remove(page)
-
-        self._currentPage = index
-        page = self._pages[index]
-        self.add(page)
-        if page._fromPage is None:
-            page._fromPage = fromPage
-            page.initialize()
-        self.show_all()
-        if fromPage is not None:
-            self.grabDefault()
-
-    @property
-    def bookedFlight(self):
-        """Get the booked flight selected."""
-        return self._bookedFlight
-
-    @property
-    def cargoWeight(self):
-        """Get the calculated ZFW value."""
-        return self._payloadPage.cargoWeight
-
-    @property
-    def zfw(self):
-        """Get the calculated ZFW value."""
-        return 0 if self._bookedFlight is None \
-               else self._payloadPage.calculateZFW()
-
-    @property
-    def filedCruiseAltitude(self):
-        """Get the filed cruise altitude."""
-        return self._routePage.filedCruiseLevel * 100
-
-    @property
-    def cruiseAltitude(self):
-        """Get the cruise altitude."""
-        return self._routePage.cruiseLevel * 100
-
-    @property
-    def route(self):
-        """Get the route."""
-        return self._routePage.route
-
-    @property
-    def departureMETAR(self):
-        """Get the METAR of the departure airport."""
-        return self._departureBriefingPage.metar
-
-    @property
-    def arrivalMETAR(self):
-        """Get the METAR of the arrival airport."""
-        return self._arrivalBriefingPage.metar
-
-    @property
-    def departureRunway(self):
-        """Get the departure runway."""
-        return self._takeoffPage.runway
-
-    @property
-    def sid(self):
-        """Get the SID."""
-        return self._takeoffPage.sid
-
-    @property
-    def v1(self):
-        """Get the V1 speed."""
-        return self._takeoffPage.v1
-
-    @property
-    def vr(self):
-        """Get the Vr speed."""
-        return self._takeoffPage.vr
-
-    @property
-    def v2(self):
-        """Get the V2 speed."""
-        return self._takeoffPage.v2
-
-    @property
-    def arrivalRunway(self):
-        """Get the arrival runway."""
-        return self._landingPage.runway
-
-    @property
-    def star(self):
-        """Get the STAR."""
-        return self._landingPage.star
-
-    @property
-    def transition(self):
-        """Get the transition."""
-        return self._landingPage.transition
-
-    @property
-    def approachType(self):
-        """Get the approach type."""
-        return self._landingPage.approachType
-
-    @property
-    def vref(self):
-        """Get the Vref speed."""
-        return self._landingPage.vref
-
-    @property
-    def flightType(self):
-        """Get the flight type."""
-        return self._finishPage.flightType
-
-    @property
-    def online(self):
-        """Get whether the flight was online or not."""
-        return self._finishPage.online
-
-    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 reset(self):
-        """Resets the wizard to go back to the login page."""
-        self._initialize()
-
-    def setStage(self, stage):
-        """Set the flight stage to the given one."""
-        if stage==const.STAGE_TAKEOFF:
-            self._takeoffPage.allowForward()
-        elif stage==const.STAGE_LANDING:
-            if not self._arrivalBriefingPage.metarEdited:
-                print "Downloading arrival METAR again"
-                self.gui.webHandler.getMETARs(self._arrivalMETARCallback,
-                                              [self._bookedFlight.arrivalICAO])
-            
-        elif stage==const.STAGE_END:
-            self._landingPage.flightEnded()
-
-    def _initialize(self):
-        """Initialize the wizard."""
-        self._fleet = None
-        self._fleetCallback = None
-        
-        self._loginResult = None
-        self._bookedFlight = None
-        self._departureGate = "-"
-        self._fuelData = 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 via the GUI and call the given callback."""
-        self._fleetCallback = callback
-        self.gui.getFleet(callback = self._fleetRetrieved, force = force)
-
-    def _fleetRetrieved(self, fleet):
-        """Callback for the fleet retrieval."""
-        self._fleet = fleet
-        if self._fleetCallback is not None:
-            self._fleetCallback(fleet)
-        self._fleetCallback = None
-        
-    def updatePlane(self, callback, tailNumber, status, gateNumber = None):
-        """Update the given plane's gate information."""
-        self.gui.updatePlane(tailNumber, status, gateNumber = gateNumber,
-                             callback = callback)
-
-    def _connectSimulator(self):
-        """Connect to the simulator."""
-        self.gui.connectSimulator(self._bookedFlight.aircraftType)
-
-    def _arrivalMETARCallback(self, returned, result):
-        """Called when the METAR of the arrival airport is retrieved."""
-        gobject.idle_add(self._handleArrivalMETAR, returned, result)
-    
-    def _handleArrivalMETAR(self, returned, result):
-        """Called when the METAR of the arrival airport is retrieved."""
-        icao = self._bookedFlight.arrivalICAO
-        if returned and icao in result.metars:
-            metar = result.metars[icao]
-            if metar!="":
-                self._arrivalBriefingPage.setMETAR(metar)
-    
-#-----------------------------------------------------------------------------
-
Index: src/mlx/gui/gates.py
===================================================================
--- src/mlx/gui/gates.py	(revision 121)
+++ src/mlx/gui/gates.py	(revision 121)
@@ -0,0 +1,172 @@
+# Module to display the status of the planes and the gates
+
+#----------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+
+#-------------------------------------------------------------------------------
+
+class FleetGateStatus(gtk.VBox):
+    """The tab to display the fleet and gate status."""
+    def __init__(self, gui):
+        """Construct the tab."""
+        super(FleetGateStatus, self).__init__()
+
+        self._gui = gui
+
+        mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                      xscale = 1.0, yscale = 1.0)
+        mainAlignment.set_padding(padding_top = 4, padding_bottom = 4,
+                                  padding_left = 12, padding_right = 12)
+        self.add(mainAlignment)
+
+        self._mainBox = mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+    
+        self._statusBox = statusBox = gtk.HBox()
+        mainBox.pack_start(statusBox, True, True, 4)
+
+        fleetFrame = gtk.Frame(label = xstr("gates_fleet_title"))
+        fleetLabel = fleetFrame.get_label_widget()
+        fleetLabel.set_use_underline(True)
+        statusBox.pack_start(fleetFrame, False, False, 4)
+
+        self._fleetStore = gtk.ListStore(str, str)
+        self._fleetList = gtk.TreeView(self._fleetStore)
+        self._fleetList.set_tooltip_markup(xstr("gates_planes_tooltip"))
+        fleetLabel.set_mnemonic_widget(self._fleetList)
+        column = gtk.TreeViewColumn(xstr("gates_tailno"), gtk.CellRendererText(),
+                                    text = 0)
+        column.set_expand(True)
+        column.set_sort_column_id(0)
+        self._fleetList.append_column(column)
+
+        column = gtk.TreeViewColumn(xstr("gates_planestatus"), gtk.CellRendererText(),
+                                    markup = 1)
+        column.set_expand(True)
+        column.set_sort_column_id(1)
+        self._fleetList.append_column(column)
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._fleetList)
+        scrolledWindow.set_size_request(200, -1)
+        # FIXME: these should be constants in common.py
+        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)
+
+
+        self._fleetAlignment =  alignment = \
+                               gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                             xscale = 0.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 4, padding_right = 4)
+        alignment.add(scrolledWindow)
+        fleetFrame.add(alignment)        
+        
+        self._gatesFrame = gatesFrame = gtk.Frame(label = xstr("gates_gates_title"))
+        statusBox.pack_start(gatesFrame, True, True, 4)        
+
+        self._gatesTable = table = gtk.Table(14, 4)
+        table.set_tooltip_markup(xstr("gates_gates_tooltip"))
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 4, padding_right = 4)
+        alignment.add(table)
+        gatesFrame.add(alignment)
+        
+        self._gateLabels = {}
+        column = 0
+        row = 0
+        for gateNumber in const.lhbpGateNumbers:
+            label = gtk.Label()
+            label.set_markup("<b>" + gateNumber + "</b>")
+            table.attach(label, column, column + 1, row, row + 1)
+
+            self._gateLabels[gateNumber] = label
+            
+            if column==1 and row==12:
+                column = 2
+                row = 1
+            elif row==13:
+                column += 1
+                row = 0
+            else:
+                row += 1
+                
+        button = gtk.Button(xstr("gates_refresh"))
+        button.set_use_underline(True)
+        button.set_tooltip_text(xstr("gates_refresh_tooltip"))
+        button.connect("clicked", self._refreshClicked)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(button)
+        mainBox.pack_start(alignment, False, False, 4)
+
+        self._fleetList.set_sensitive(False)
+        self._gatesTable.set_sensitive(False)
+        
+    def disable(self):
+        """Disable the widget."""
+        self._mainBox.set_sensitive(False)
+    
+    def enable(self):
+        """Enable the widget."""
+        self._mainBox.set_sensitive(True)
+
+    def _refreshClicked(self, button):
+        """Called when the Refresh data button is clicked."""
+        self._gui.getFleet(force = True)
+
+    def handleFleet(self, fleet):
+        """Handle new fleet information.
+
+        If fleet is None, the data will be cleared."""
+        self._fleetStore.clear()
+        if fleet is None:
+            for (gateNumber, label) in self._gateLabels.iteritems():
+                label.set_markup("<b>" + gateNumber + "</b>")
+        else:        
+            for plane in fleet:
+                conflicting = False
+                tailNumber = plane.tailNumber
+                if plane.status==const.PLANE_HOME:
+                    status = "LHBP - %s" % (plane.gateNumber,)
+                    conflicting = fleet.isGateConflicting(plane)
+                elif plane.status==const.PLANE_AWAY:
+                    status = xstr("gates_plane_away")
+                elif plane.status==const.PLANE_PARKING:
+                    status = xstr("gates_plane_parking")
+                else:
+                    status = xstr("gates_plane_unknown")
+
+                if conflicting:
+                    tailNumber = '<span foreground="red">' + tailNumber + '</span>'
+                    status = '<span foreground="red">' + status + '</span>'
+
+                self._fleetStore.append([tailNumber, status])
+            # FIXME: this should be a constant in common.py
+            self._fleetStore.set_sort_column_id(0,
+                                                gtk.SortType.ASCENDING if pygobject
+                                                else gtk.SORT_ASCENDING)
+
+            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
+            for gateNumber in const.lhbpGateNumbers:
+                markup = gateNumber
+                if gateNumber in occupiedGateNumbers:
+                    markup = '<span foreground="orange">' + markup + '</span>'
+                markup = '<b>' + markup + '</b>'
+                self._gateLabels[gateNumber].set_markup(markup)
+            
+        self._fleetList.set_sensitive(fleet is not None)
+        self._gatesTable.set_sensitive(fleet is not None)
+
+#----------------------------------------------------------------------------
Index: src/mlx/gui/gates.py
===================================================================
--- src/mlx/gui/gates.py	(revision 121)
+++ 	(revision )
@@ -1,172 +1,0 @@
-# Module to display the status of the planes and the gates
-
-#----------------------------------------------------------------------------
-
-from mlx.gui.common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-
-#-------------------------------------------------------------------------------
-
-class FleetGateStatus(gtk.VBox):
-    """The tab to display the fleet and gate status."""
-    def __init__(self, gui):
-        """Construct the tab."""
-        super(FleetGateStatus, self).__init__()
-
-        self._gui = gui
-
-        mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                      xscale = 1.0, yscale = 1.0)
-        mainAlignment.set_padding(padding_top = 4, padding_bottom = 4,
-                                  padding_left = 12, padding_right = 12)
-        self.add(mainAlignment)
-
-        self._mainBox = mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-    
-        self._statusBox = statusBox = gtk.HBox()
-        mainBox.pack_start(statusBox, True, True, 4)
-
-        fleetFrame = gtk.Frame(label = xstr("gates_fleet_title"))
-        fleetLabel = fleetFrame.get_label_widget()
-        fleetLabel.set_use_underline(True)
-        statusBox.pack_start(fleetFrame, False, False, 4)
-
-        self._fleetStore = gtk.ListStore(str, str)
-        self._fleetList = gtk.TreeView(self._fleetStore)
-        self._fleetList.set_tooltip_markup(xstr("gates_planes_tooltip"))
-        fleetLabel.set_mnemonic_widget(self._fleetList)
-        column = gtk.TreeViewColumn(xstr("gates_tailno"), gtk.CellRendererText(),
-                                    text = 0)
-        column.set_expand(True)
-        column.set_sort_column_id(0)
-        self._fleetList.append_column(column)
-
-        column = gtk.TreeViewColumn(xstr("gates_planestatus"), gtk.CellRendererText(),
-                                    markup = 1)
-        column.set_expand(True)
-        column.set_sort_column_id(1)
-        self._fleetList.append_column(column)
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._fleetList)
-        scrolledWindow.set_size_request(200, -1)
-        # FIXME: these should be constants in common.py
-        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)
-
-
-        self._fleetAlignment =  alignment = \
-                               gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                             xscale = 0.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 4, padding_right = 4)
-        alignment.add(scrolledWindow)
-        fleetFrame.add(alignment)        
-        
-        self._gatesFrame = gatesFrame = gtk.Frame(label = xstr("gates_gates_title"))
-        statusBox.pack_start(gatesFrame, True, True, 4)        
-
-        self._gatesTable = table = gtk.Table(14, 4)
-        table.set_tooltip_markup(xstr("gates_gates_tooltip"))
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 4, padding_right = 4)
-        alignment.add(table)
-        gatesFrame.add(alignment)
-        
-        self._gateLabels = {}
-        column = 0
-        row = 0
-        for gateNumber in const.lhbpGateNumbers:
-            label = gtk.Label()
-            label.set_markup("<b>" + gateNumber + "</b>")
-            table.attach(label, column, column + 1, row, row + 1)
-
-            self._gateLabels[gateNumber] = label
-            
-            if column==1 and row==12:
-                column = 2
-                row = 1
-            elif row==13:
-                column += 1
-                row = 0
-            else:
-                row += 1
-                
-        button = gtk.Button(xstr("gates_refresh"))
-        button.set_use_underline(True)
-        button.set_tooltip_text(xstr("gates_refresh_tooltip"))
-        button.connect("clicked", self._refreshClicked)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(button)
-        mainBox.pack_start(alignment, False, False, 4)
-
-        self._fleetList.set_sensitive(False)
-        self._gatesTable.set_sensitive(False)
-        
-    def disable(self):
-        """Disable the widget."""
-        self._mainBox.set_sensitive(False)
-    
-    def enable(self):
-        """Enable the widget."""
-        self._mainBox.set_sensitive(True)
-
-    def _refreshClicked(self, button):
-        """Called when the Refresh data button is clicked."""
-        self._gui.getFleet(force = True)
-
-    def handleFleet(self, fleet):
-        """Handle new fleet information.
-
-        If fleet is None, the data will be cleared."""
-        self._fleetStore.clear()
-        if fleet is None:
-            for (gateNumber, label) in self._gateLabels.iteritems():
-                label.set_markup("<b>" + gateNumber + "</b>")
-        else:        
-            for plane in fleet:
-                conflicting = False
-                tailNumber = plane.tailNumber
-                if plane.status==const.PLANE_HOME:
-                    status = "LHBP - %s" % (plane.gateNumber,)
-                    conflicting = fleet.isGateConflicting(plane)
-                elif plane.status==const.PLANE_AWAY:
-                    status = xstr("gates_plane_away")
-                elif plane.status==const.PLANE_PARKING:
-                    status = xstr("gates_plane_parking")
-                else:
-                    status = xstr("gates_plane_unknown")
-
-                if conflicting:
-                    tailNumber = '<span foreground="red">' + tailNumber + '</span>'
-                    status = '<span foreground="red">' + status + '</span>'
-
-                self._fleetStore.append([tailNumber, status])
-            # FIXME: this should be a constant in common.py
-            self._fleetStore.set_sort_column_id(0,
-                                                gtk.SortType.ASCENDING if pygobject
-                                                else gtk.SORT_ASCENDING)
-
-            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
-            for gateNumber in const.lhbpGateNumbers:
-                markup = gateNumber
-                if gateNumber in occupiedGateNumbers:
-                    markup = '<span foreground="orange">' + markup + '</span>'
-                markup = '<b>' + markup + '</b>'
-                self._gateLabels[gateNumber].set_markup(markup)
-            
-        self._fleetList.set_sensitive(fleet is not None)
-        self._gatesTable.set_sensitive(fleet is not None)
-
-#----------------------------------------------------------------------------
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 164)
+++ src/mlx/gui/gui.py	(revision 164)
@@ -0,0 +1,1107 @@
+# The main file for the GUI
+
+from statusicon import StatusIcon
+from statusbar import Statusbar
+from info import FlightInfo
+from update import Updater
+from mlx.gui.common import *
+from mlx.gui.flight import Wizard
+from mlx.gui.monitor import MonitorWindow
+from mlx.gui.weighthelp import WeightHelp
+from mlx.gui.gates import FleetGateStatus
+from mlx.gui.prefs import Preferences
+
+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
+from  mlx.i18n import xstr
+from mlx.pirep import PIREP
+
+import time
+import threading
+import sys
+
+#------------------------------------------------------------------------------
+
+class GUI(fs.ConnectionListener):
+    """The main GUI class."""
+    @staticmethod
+    def _formatFlightLogLine(timeStr, line):
+        """Format the given line for flight logging."""
+        if timeStr is not None:
+            line = timeStr + ": " + line
+        return line + "\n"
+        
+    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(self)
+        self._flight = None
+        self._simulator = None
+        self._monitoring = False
+
+        self._fleet = None
+
+        self._fleetCallback = None
+
+        self._updatePlaneCallback = None
+        self._updatePlaneTailNumber = None
+        self._updatePlaneStatus = None
+        self._updatePlaneGateNumber = None
+
+        self._stdioLock = threading.Lock()
+        self._stdioText = ""
+
+        self._sendPIREPCallback = None
+
+        self.webHandler = web.Handler()
+        self.webHandler.start()
+
+        self.toRestart = False
+
+    def build(self, iconDirectory):
+        """Build the GUI."""
+        
+        self._mainWindow = window = gtk.Window()
+        window.set_title(WINDOW_TITLE_BASE)
+        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)
+        accelGroup = gtk.AccelGroup()
+        window.add_accel_group(accelGroup)
+
+        mainVBox = gtk.VBox()
+        window.add(mainVBox)
+
+        self._preferences = Preferences(self)
+
+        menuBar = self._buildMenuBar(accelGroup)
+        mainVBox.pack_start(menuBar, False, False, 0)
+
+        self._notebook = gtk.Notebook()
+        mainVBox.pack_start(self._notebook, True, True, 4)
+        
+        self._wizard = Wizard(self)
+        label = gtk.Label(xstr("tab_flight"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("tab_flight_tooltip"))
+        self._notebook.append_page(self._wizard, label)
+
+        self._flightInfo = FlightInfo(self)
+        label = gtk.Label(xstr("tab_flight_info"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
+        self._notebook.append_page(self._flightInfo, label)
+        self._flightInfo.disable()
+
+        self._weightHelp = WeightHelp(self)
+        label = gtk.Label(xstr("tab_weight_help"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
+        self._notebook.append_page(self._weightHelp, label)
+        
+        (logWidget, self._logView)  = self._buildLogWidget()
+        label = gtk.Label(xstr("tab_log"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("tab_log_tooltip"))
+        self._notebook.append_page(logWidget, label)
+
+        self._fleetGateStatus = FleetGateStatus(self)
+        label = gtk.Label(xstr("tab_gates"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("tab_gates_tooltip"))
+        self._notebook.append_page(self._fleetGateStatus, label)
+        
+        (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
+        self._debugLogWidget.show_all()
+
+        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
+
+        self._statusbar = Statusbar()
+        mainVBox.pack_start(self._statusbar, False, False, 0)
+
+        self._notebook.connect("switch-page", self._notebookPageSwitch)
+
+        self._monitorWindow = MonitorWindow(self, iconDirectory)
+        self._monitorWindow.add_accel_group(accelGroup)
+        self._monitorWindowX = None
+        self._monitorWindowY = None
+        self._selfToggling = False
+
+        window.show_all()
+        self._wizard.grabDefault()
+        self._weightHelp.reset()
+        self._weightHelp.disable()
+
+        self._statusIcon = StatusIcon(iconDirectory, self)
+
+        self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
+                                      else gdk.WATCH)
+
+        self._loadPIREPDialog = None
+        self._lastLoadedPIREP = None
+
+    @property
+    def mainWindow(self):
+        """Get the main window of the GUI."""
+        return self._mainWindow
+        
+    @property
+    def logger(self):
+        """Get the logger used by us."""
+        return self._logger
+        
+    @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
+
+    @property
+    def loginResult(self):
+        """Get the result of the login."""
+        return self._wizard.loginResult
+
+    @property
+    def bookedFlight(self):
+        """Get the booked flight selected, if any."""
+        return self._wizard.bookedFlight
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight."""
+        return self._wizard.cargoWeight
+
+    @property
+    def zfw(self):
+        """Get Zero-Fuel Weight calculated for the current flight."""
+        return self._wizard.zfw
+        
+    @property
+    def filedCruiseAltitude(self):
+        """Get cruise altitude filed for the current flight."""
+        return self._wizard.filedCruiseAltitude
+        
+    @property
+    def cruiseAltitude(self):
+        """Get cruise altitude set for the current flight."""
+        return self._wizard.cruiseAltitude
+
+    @property
+    def route(self):
+        """Get the flight route."""
+        return self._wizard.route
+
+    @property
+    def departureMETAR(self):
+        """Get the METAR of the deprature airport."""
+        return self._wizard.departureMETAR
+        
+    @property
+    def arrivalMETAR(self):
+        """Get the METAR of the deprature airport."""
+        return self._wizard.arrivalMETAR
+
+    @property
+    def departureRunway(self):
+        """Get the name of the departure runway."""
+        return self._wizard.departureRunway
+        
+    @property
+    def sid(self):
+        """Get the SID."""
+        return self._wizard.sid
+
+    @property
+    def v1(self):
+        """Get the V1 speed calculated for the flight."""
+        return self._wizard.v1
+        
+    @property
+    def vr(self):
+        """Get the Vr speed calculated for the flight."""
+        return self._wizard.vr
+        
+    @property
+    def v2(self):
+        """Get the V2 speed calculated for the flight."""
+        return self._wizard.v2
+        
+    @property
+    def arrivalRunway(self):
+        """Get the arrival runway."""
+        return self._wizard.arrivalRunway
+
+    @property
+    def star(self):
+        """Get the STAR."""
+        return self._wizard.star
+
+    @property
+    def transition(self):
+        """Get the transition."""
+        return self._wizard.transition
+
+    @property
+    def approachType(self):
+        """Get the approach type."""
+        return self._wizard.approachType
+
+    @property
+    def vref(self):
+        """Get the Vref speed calculated for the flight."""
+        return self._wizard.vref
+        
+    @property
+    def flightType(self):
+        """Get the flight type."""
+        return self._wizard.flightType
+
+    @property
+    def online(self):
+        """Get whether the flight was online or not."""
+        return self._wizard.online
+
+    @property
+    def comments(self):
+        """Get the comments."""
+        return self._flightInfo.comments
+
+    @property
+    def flightDefects(self):
+        """Get the flight defects."""
+        return self._flightInfo.flightDefects
+
+    @property
+    def delayCodes(self):
+        """Get the delay codes."""
+        return self._flightInfo.delayCodes
+
+    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()
+
+        self._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,))
+        fs.sendMessage(const.MESSAGETYPE_INFORMATION,
+                       "Welcome to MAVA Logger X " + const.VERSION)
+        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(parent = self._mainWindow,
+                                   type = MESSAGETYPE_ERROR,
+                                   message_format = xstr("conn_failed"))
+    
+        dialog.set_title(WINDOW_TITLE_BASE)
+        dialog.format_secondary_markup(xstr("conn_failed_sec"))
+        
+        dialog.add_button(xstr("button_cancel"), 0)
+        dialog.add_button(xstr("button_tryagain"), 1)
+        dialog.set_default_response(1)
+        
+        result = dialog.run()
+        dialog.hide()
+        if result == 1:
+            self.beginBusy(xstr("connect_busy"))
+            self._simulator.reconnect()
+        else:
+            self.reset()
+        
+    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 = xstr("conn_broken"),
+                                   parent = self._mainWindow)
+        dialog.set_title(WINDOW_TITLE_BASE)
+        dialog.format_secondary_markup(xstr("conn_broken_sec"))
+
+        dialog.add_button(xstr("button_cancel"), 0)
+        dialog.add_button(xstr("button_reconnect"), 1)
+        dialog.set_default_response(1)
+
+        result = dialog.run()
+        dialog.hide()
+        if result == 1:
+            self.beginBusy(xstr("connect_busy"))
+            self._reconnecting = True
+            self._simulator.reconnect()
+        else:
+            self.reset()
+
+    def enableFlightInfo(self):
+        """Enable the flight info tab."""
+        self._flightInfo.enable()
+
+    def reset(self):
+        """Reset the GUI."""
+        self._disconnect()
+
+        self._flightInfo.reset()
+        self._flightInfo.disable()
+        self.resetFlightStatus()
+
+        self._weightHelp.reset()
+        self._weightHelp.disable()
+        self._wizard.reset()
+        self._notebook.set_current_page(0)
+
+        self._logView.get_buffer().set_text("")
+
+    def _disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator if connected."""
+        self.stopMonitoring()
+
+        if self._connected:
+            if closingMessage is None:
+                self._flight.simulator.disconnect()
+            else:
+                fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
+                               closingMessage, duration,
+                               disconnect = True)
+            self._connected = False
+
+        self._connecting = False
+        self._reconnecting = False
+        self._statusbar.updateConnection(False, False)
+        self._weightHelp.disable()
+
+        return True
+            
+    def addFlightLogLine(self, timeStr, line):
+        """Write the given message line to the log."""
+        gobject.idle_add(self._writeLog,
+                         GUI._formatFlightLogLine(timeStr, line),
+                         self._logView)
+
+    def updateFlightLogLine(self, index, timeStr, line):
+        """Update the line with the given index."""
+        gobject.idle_add(self._updateFlightLogLine, index,
+                         GUI._formatFlightLogLine(timeStr, line))
+
+    def _updateFlightLogLine(self, index, line):
+        """Replace the contents of the given line in the log."""
+        buffer = self._logView.get_buffer()
+        startIter = buffer.get_iter_at_line(index)
+        endIter = buffer.get_iter_at_line(index + 1)
+        buffer.delete(startIter, endIter)
+        buffer.insert(startIter, line)
+        self._logView.scroll_mark_onscreen(buffer.get_insert())
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Update the data."""
+        gobject.idle_add(self._monitorWindow.setData, state)
+        gobject.idle_add(self._statusbar.updateTime, state.timestamp)
+
+    def resetFlightStatus(self):
+        """Reset the status of the flight."""
+        self._statusbar.resetFlightStatus()
+        self._statusbar.updateTime()
+        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)
+        self._wizard.setStage(stage)
+        if stage==const.STAGE_END:
+            self._disconnect(closingMessage =
+                             "Flight plan closed. Welcome to %s" % \
+                             (self.bookedFlight.arrivalICAO,),
+                             duration = 5)
+
+    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 self.config.hideMinimizedWindow and \
+           (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()
+        if self._showMonitorMenuItem.get_active():
+            self._selfToggling = True
+            self._showMonitorMenuItem.set_active(False)
+        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()
+        if not self._showMonitorMenuItem.get_active():
+            self._selfToggling = True
+            self._showMonitorMenuItem.set_active(True)
+
+    def _toggleMonitorWindow(self, menuItem):
+        if self._selfToggling:
+            self._selfToggling = False
+        elif self._monitorWindow.get_visible():
+            self.hideMonitorWindow()
+        else:
+            self.showMonitorWindow()
+
+    def restart(self):
+        """Quit and restart the application."""
+        self.toRestart = True
+        self._quit(force = True)
+
+    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._wizard.set_sensitive(False)
+        self._weightHelp.set_sensitive(False)
+        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._weightHelp.set_sensitive(True)
+        self._wizard.set_sensitive(True)
+        self._statusbar.updateBusyState(None)
+
+    def initializeWeightHelp(self):
+        """Initialize the weight help tab."""
+        self._weightHelp.reset()
+        self._weightHelp.enable()
+
+    def getFleetAsync(self, callback = None, force = None):
+        """Get the fleet asynchronously."""
+        gobject.idle_add(self.getFleet, callback, force)
+
+    def getFleet(self, callback = None, force = False):
+        """Get the fleet.
+
+        If force is False, and we already have a fleet retrieved,
+        that one will be used."""
+        if self._fleet is None or force:
+            self._fleetCallback = callback
+            self.beginBusy(xstr("fleet_busy"))
+            self.webHandler.getFleet(self._fleetResultCallback)
+        else:
+            callback(self._fleet)
+
+    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.endBusy()
+        if returned:
+            self._fleet = result.fleet
+        else:
+            self._fleet = None
+
+            dialog = gtk.MessageDialog(parent = self.mainWindow,
+                                       type = MESSAGETYPE_ERROR,
+                                       message_format = xstr("fleet_failed"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.run()
+            dialog.hide()
+
+        callback = self._fleetCallback
+        self._fleetCallback = None
+        if  callback is not None:
+            callback(self._fleet)
+        self._fleetGateStatus.handleFleet(self._fleet)
+
+    def updatePlane(self, tailNumber, status,
+                    gateNumber = None, callback = None):
+        """Update the status of the given plane."""
+        self.beginBusy(xstr("fleet_update_busy"))
+
+        self._updatePlaneCallback = callback
+
+        self._updatePlaneTailNumber = tailNumber
+        self._updatePlaneStatus = status
+        self._updatePlaneGateNumber = gateNumber
+        
+        self.webHandler.updatePlane(self._updatePlaneResultCallback,
+                                    tailNumber, status, gateNumber)
+
+    def _updatePlaneResultCallback(self, returned, result):
+        """Called when the status of a plane has been updated."""
+        gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
+
+    def _handleUpdatePlaneResult(self, returned, result):
+        """Handle the plane update result."""
+        self.endBusy()
+        if returned:
+            success = result.success
+            if success:
+                if self._fleet is not None:
+                    self._fleet.updatePlane(self._updatePlaneTailNumber,
+                                            self._updatePlaneStatus,
+                                            self._updatePlaneGateNumber)
+                    self._fleetGateStatus.handleFleet(self._fleet)
+        else:
+            dialog = gtk.MessageDialog(parent = self.mainWindow,
+                                       type = MESSAGETYPE_ERROR,
+                                       message_format = xstr("fleet_update_failed"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            dialog.run()
+            dialog.hide()
+
+            success = None
+
+        callback = self._updatePlaneCallback
+        self._updatePlaneCallback = None
+        if callback is not None:
+            callback(success)
+
+    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:
+            #print >> sys.__stdout__, line
+            self._writeLog(line + "\n", self._debugLogView)
+
+        if text:
+            #print >> sys.__stdout__, text,
+            self._writeLog(text, self._debugLogView)
+
+    def connectSimulator(self, aircraftType):
+        """Connect to the simulator for the first time."""
+        self._logger.reset()
+
+        self._flight = flight.Flight(self._logger, self)
+        self._flight.flareTimeFromFS = self.config.flareTimeFromFS
+        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)
+            fs.setupMessageSending(self.config, self._simulator)
+            self._setupTimeSync()
+        
+        self._flight.simulator = self._simulator
+
+        self.beginBusy(xstr("connect_busy"))
+        self._statusbar.updateConnection(self._connecting, self._connected)
+
+        self._connecting = True
+        self._simulator.connect(self._flight.aircraft)        
+
+    def startMonitoring(self):
+        """Start monitoring."""
+        if not self._monitoring:
+            self.simulator.startMonitoring()
+            self._monitoring = True
+
+    def stopMonitoring(self):
+        """Stop monitoring."""
+        if self._monitoring:
+            self.simulator.stopMonitoring()
+            self._monitoring = False
+
+    def _buildMenuBar(self, accelGroup):
+        """Build the main menu bar."""
+        menuBar = gtk.MenuBar()
+        
+        fileMenuItem = gtk.MenuItem(xstr("menu_file"))
+        fileMenu = gtk.Menu()
+        fileMenuItem.set_submenu(fileMenu)
+        menuBar.append(fileMenuItem)
+
+        loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
+        loadPIREPMenuItem.set_use_stock(True)
+        loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
+        loadPIREPMenuItem.add_accelerator("activate", accelGroup,
+                                          ord(xstr("menu_file_loadPIREP_key")),
+                                          CONTROL_MASK, ACCEL_VISIBLE)
+        loadPIREPMenuItem.connect("activate", self._loadPIREP)
+        fileMenu.append(loadPIREPMenuItem)
+
+        fileMenu.append(gtk.SeparatorMenuItem())
+
+        quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
+        quitMenuItem.set_use_stock(True)
+        quitMenuItem.set_label(xstr("menu_file_quit"))
+        quitMenuItem.add_accelerator("activate", accelGroup,
+                                     ord(xstr("menu_file_quit_key")),
+                                     CONTROL_MASK, ACCEL_VISIBLE)
+        quitMenuItem.connect("activate", self._quit)
+        fileMenu.append(quitMenuItem)
+
+        toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
+        toolsMenu = gtk.Menu()
+        toolsMenuItem.set_submenu(toolsMenu)
+        menuBar.append(toolsMenuItem)
+
+        prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
+        prefsMenuItem.set_use_stock(True)
+        prefsMenuItem.set_label(xstr("menu_tools_prefs"))
+        prefsMenuItem.add_accelerator("activate", accelGroup,
+                                      ord(xstr("menu_tools_prefs_key")),
+                                      CONTROL_MASK, ACCEL_VISIBLE)
+        prefsMenuItem.connect("activate", self._editPreferences)
+        toolsMenu.append(prefsMenuItem)
+
+        viewMenuItem = gtk.MenuItem(xstr("menu_view"))
+        viewMenu = gtk.Menu()
+        viewMenuItem.set_submenu(viewMenu)
+        menuBar.append(viewMenuItem)
+
+        self._showMonitorMenuItem = gtk.CheckMenuItem()
+        self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
+        self._showMonitorMenuItem.set_use_underline(True)
+        self._showMonitorMenuItem.set_active(False)
+        self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
+                                                  ord(xstr("menu_view_monitor_key")),
+                                                  CONTROL_MASK, ACCEL_VISIBLE)
+        self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
+        viewMenu.append(self._showMonitorMenuItem)
+
+        showDebugMenuItem = gtk.CheckMenuItem()
+        showDebugMenuItem.set_label(xstr("menu_view_debug"))
+        showDebugMenuItem.set_use_underline(True)
+        showDebugMenuItem.set_active(False)
+        showDebugMenuItem.add_accelerator("activate", accelGroup,
+                                          ord(xstr("menu_view_debug_key")),
+                                          CONTROL_MASK, ACCEL_VISIBLE)
+        showDebugMenuItem.connect("toggled", self._toggleDebugLog)
+        viewMenu.append(showDebugMenuItem)
+
+        return menuBar
+
+    def _toggleDebugLog(self, menuItem):
+        """Toggle the debug log."""
+        if menuItem.get_active():
+            label = gtk.Label(xstr("tab_debug_log"))
+            label.set_use_underline(True)
+            label.set_tooltip_text(xstr("tab_debug_log_tooltip"))        
+            self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
+            self._notebook.set_current_page(self._debugLogPage)
+        else:
+            self._notebook.remove_page(self._debugLogPage)
+
+    def _buildLogWidget(self):
+        """Build the widget for the log."""
+        alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        alignment.set_padding(padding_top = 8, padding_bottom = 8,
+                              padding_left = 16, padding_right = 16)
+
+        logScroller = gtk.ScrolledWindow()
+        # FIXME: these should be constants in common
+        logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                               else gtk.POLICY_AUTOMATIC,
+                               gtk.PolicyType.AUTOMATIC if pygobject
+                               else gtk.POLICY_AUTOMATIC)
+        logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                    else gtk.SHADOW_IN)
+        logView = gtk.TextView()
+        logView.set_editable(False)
+        logScroller.add(logView)
+
+        logBox = gtk.VBox()
+        logBox.pack_start(logScroller, True, True, 0)
+        logBox.set_size_request(-1, 200)
+
+        alignment.add(logBox)
+
+        return (alignment, logView)
+
+    def _writeLog(self, msg, logView):
+        """Write the given message to the log."""
+        buffer = logView.get_buffer()
+        buffer.insert(buffer.get_end_iter(), msg)
+        logView.scroll_mark_onscreen(buffer.get_insert())
+
+    def _quit(self, what = None, force = False):
+        """Quit from the application."""
+        if force:
+            result=RESPONSETYPE_YES
+        else:
+            dialog = gtk.MessageDialog(parent = self._mainWindow,
+                                       type = MESSAGETYPE_QUESTION,
+                                       message_format = xstr("quit_question"))
+
+            dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
+            dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
+
+            dialog.set_title(WINDOW_TITLE_BASE)
+            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)
+
+    def _editPreferences(self, menuItem):
+        """Callback for editing the preferences."""
+        self._preferences.run(self.config)
+        self._setupTimeSync()
+
+    def _setupTimeSync(self):
+        """Enable or disable the simulator time synchronization based on the
+        configuration."""
+        simulator = self._simulator
+        if simulator is not None:
+            if self.config.syncFSTime:
+                simulator.enableTimeSync()
+            else:
+                simulator.disableTimeSync()
+
+    def _loadPIREP(self, menuItem):
+        """Load a PIREP for sending."""
+        dialog = self._getLoadPirepDialog()
+
+        if self._lastLoadedPIREP:
+            dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
+        else:
+            pirepDirectory = self.config.pirepDirectory
+            if pirepDirectory is not None:
+                dialog.set_current_folder(pirepDirectory)
+        
+        result = dialog.run()
+        dialog.hide()
+
+        if result==RESPONSETYPE_OK:
+            self._lastLoadedPIREP = text2unicode(dialog.get_filename())
+
+            pirep = PIREP.load(self._lastLoadedPIREP)
+            if pirep is None:
+                dialog = gtk.MessageDialog(parent = self._mainWindow,
+                                           type = MESSAGETYPE_ERROR,
+                                           message_format = xstr("loadPIREP_failed"))
+                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+                dialog.set_title(WINDOW_TITLE_BASE)
+                dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
+                dialog.run()
+                dialog.hide()
+            else:
+                dialog = self._getSendLoadedDialog(pirep)
+                dialog.show_all()
+                result = dialog.run()
+                dialog.hide()
+
+                if result==RESPONSETYPE_OK:
+                    self.sendPIREP(pirep)
+
+    def _getLoadPirepDialog(self):
+        """Get the PIREP loading file chooser dialog.
+
+        If it is not created yet, it will be created."""
+        if self._loadPIREPDialog is None:
+            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                           xstr("loadPIREP_browser_title"),
+                                           action = FILE_CHOOSER_ACTION_OPEN,
+                                           buttons = (gtk.STOCK_CANCEL,
+                                                      RESPONSETYPE_CANCEL,
+                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
+                                           parent = self._mainWindow)
+            dialog.set_modal(True)
+            
+
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("loadPIREP_filter_pireps"))
+            filter.add_pattern("*.pirep")
+            dialog.add_filter(filter)
+            
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("loadPIREP_filter_all"))
+            filter.add_pattern("*.*")
+            dialog.add_filter(filter)
+
+            self._loadPIREPDialog = dialog
+
+        return self._loadPIREPDialog
+
+    def _getSendLoadedDialog(self, pirep):
+        """Get a dialog displaying the main information of the flight from the
+        PIREP and providing Cancel and Send buttons."""
+        dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
+                            xstr("loadPIREP_send_title"),
+                            parent = self._mainWindow,
+                            flags = DIALOG_MODAL)
+
+        contentArea = dialog.get_content_area()
+
+        label = gtk.Label(xstr("loadPIREP_send_help"))
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.set_padding(padding_top = 16, padding_bottom = 0,
+                              padding_left = 48, padding_right = 48)
+        alignment.add(label)
+        contentArea.pack_start(alignment, False, False, 8)
+
+        table = gtk.Table(5, 2)
+        tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
+                                   padding_left = 48, padding_right = 48)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        tableAlignment.add(table)
+        contentArea.pack_start(tableAlignment, True, True, 8)
+
+        bookedFlight = pirep.bookedFlight
+
+        label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
+        label.set_use_markup(True)
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 0, 1)
+        
+        label = gtk.Label(bookedFlight.callsign)
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 1, 2, 0, 1)
+
+        label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
+        label.set_use_markup(True)
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 1, 2)
+        
+        label = gtk.Label(str(bookedFlight.departureTime.date()))
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 1, 2, 1, 2)
+
+        label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
+        label.set_use_markup(True)
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 2, 3)
+        
+        label = gtk.Label(bookedFlight.departureICAO)
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 1, 2, 2, 3)
+
+        label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
+        label.set_use_markup(True)
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 3, 4)
+        
+        label = gtk.Label(bookedFlight.arrivalICAO)
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 1, 2, 3, 4)
+
+        label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
+        label.set_use_markup(True)
+        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, 4, 5)
+
+        rating = pirep.rating
+        label = gtk.Label()
+        if rating<0:
+            label.set_markup('<b><span foreground="red">NO GO</span></b>')
+        else:
+            label.set_text("%.1f %%" % (rating,))
+        
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 1, 2, 4, 5)
+
+        dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
+        dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
+        
+        return dialog
+                            
+    def sendPIREP(self, pirep, callback = None):
+        """Send the given PIREP."""
+        self.beginBusy(xstr("sendPIREP_busy"))
+        self._sendPIREPCallback = callback
+        self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
+
+    def _pirepSentCallback(self, returned, result):
+        """Callback for the PIREP sending result."""
+        gobject.idle_add(self._handlePIREPSent, returned, result)
+
+    def _handlePIREPSent(self, returned, result):
+        """Callback for the PIREP sending result."""
+        self.endBusy()
+        secondaryMarkup = None
+        type = MESSAGETYPE_ERROR
+        if returned:
+            if result.success:
+                type = MESSAGETYPE_INFO
+                messageFormat = xstr("sendPIREP_success")
+                secondaryMarkup = xstr("sendPIREP_success_sec")
+            elif result.alreadyFlown:
+                messageFormat = xstr("sendPIREP_already")
+                secondaryMarkup = xstr("sendPIREP_already_sec")
+            elif result.notAvailable:
+                messageFormat = xstr("sendPIREP_notavail")
+            else:
+                messageFormat = xstr("sendPIREP_unknown")
+                secondaryMarkup = xstr("sendPIREP_unknown_sec")
+        else:
+            print "PIREP sending failed", result
+            messageFormat = xstr("sendPIREP_failed")
+            secondaryMarkup = xstr("sendPIREP_failed_sec")
+        
+        dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
+                                   type = type, message_format = messageFormat)
+        dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+        dialog.set_title(WINDOW_TITLE_BASE)
+        if secondaryMarkup is not None:
+            dialog.format_secondary_markup(secondaryMarkup)
+
+        dialog.run()
+        dialog.hide()
+
+        callback = self._sendPIREPCallback
+        self._sendPIREPCallback = None
+        if callback is not None:
+            callback(returned, result)
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 152)
+++ 	(revision )
@@ -1,1107 +1,0 @@
-# The main file for the GUI
-
-from statusicon import StatusIcon
-from statusbar import Statusbar
-from info import FlightInfo
-from update import Updater
-from mlx.gui.common import *
-from mlx.gui.flight import Wizard
-from mlx.gui.monitor import MonitorWindow
-from mlx.gui.weighthelp import WeightHelp
-from mlx.gui.gates import FleetGateStatus
-from mlx.gui.prefs import Preferences
-
-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
-from  mlx.i18n import xstr
-from mlx.pirep import PIREP
-
-import time
-import threading
-import sys
-
-#------------------------------------------------------------------------------
-
-class GUI(fs.ConnectionListener):
-    """The main GUI class."""
-    @staticmethod
-    def _formatFlightLogLine(timeStr, line):
-        """Format the given line for flight logging."""
-        if timeStr is not None:
-            line = timeStr + ": " + line
-        return line + "\n"
-        
-    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(self)
-        self._flight = None
-        self._simulator = None
-        self._monitoring = False
-
-        self._fleet = None
-
-        self._fleetCallback = None
-
-        self._updatePlaneCallback = None
-        self._updatePlaneTailNumber = None
-        self._updatePlaneStatus = None
-        self._updatePlaneGateNumber = None
-
-        self._stdioLock = threading.Lock()
-        self._stdioText = ""
-
-        self._sendPIREPCallback = None
-
-        self.webHandler = web.Handler()
-        self.webHandler.start()
-
-        self.toRestart = False
-
-    def build(self, iconDirectory):
-        """Build the GUI."""
-        
-        self._mainWindow = window = gtk.Window()
-        window.set_title(WINDOW_TITLE_BASE)
-        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)
-        accelGroup = gtk.AccelGroup()
-        window.add_accel_group(accelGroup)
-
-        mainVBox = gtk.VBox()
-        window.add(mainVBox)
-
-        self._preferences = Preferences(self)
-
-        menuBar = self._buildMenuBar(accelGroup)
-        mainVBox.pack_start(menuBar, False, False, 0)
-
-        self._notebook = gtk.Notebook()
-        mainVBox.pack_start(self._notebook, True, True, 4)
-        
-        self._wizard = Wizard(self)
-        label = gtk.Label(xstr("tab_flight"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("tab_flight_tooltip"))
-        self._notebook.append_page(self._wizard, label)
-
-        self._flightInfo = FlightInfo(self)
-        label = gtk.Label(xstr("tab_flight_info"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
-        self._notebook.append_page(self._flightInfo, label)
-        self._flightInfo.disable()
-
-        self._weightHelp = WeightHelp(self)
-        label = gtk.Label(xstr("tab_weight_help"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
-        self._notebook.append_page(self._weightHelp, label)
-        
-        (logWidget, self._logView)  = self._buildLogWidget()
-        label = gtk.Label(xstr("tab_log"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("tab_log_tooltip"))
-        self._notebook.append_page(logWidget, label)
-
-        self._fleetGateStatus = FleetGateStatus(self)
-        label = gtk.Label(xstr("tab_gates"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("tab_gates_tooltip"))
-        self._notebook.append_page(self._fleetGateStatus, label)
-        
-        (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
-        self._debugLogWidget.show_all()
-
-        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
-
-        self._statusbar = Statusbar()
-        mainVBox.pack_start(self._statusbar, False, False, 0)
-
-        self._notebook.connect("switch-page", self._notebookPageSwitch)
-
-        self._monitorWindow = MonitorWindow(self, iconDirectory)
-        self._monitorWindow.add_accel_group(accelGroup)
-        self._monitorWindowX = None
-        self._monitorWindowY = None
-        self._selfToggling = False
-
-        window.show_all()
-        self._wizard.grabDefault()
-        self._weightHelp.reset()
-        self._weightHelp.disable()
-
-        self._statusIcon = StatusIcon(iconDirectory, self)
-
-        self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
-                                      else gdk.WATCH)
-
-        self._loadPIREPDialog = None
-        self._lastLoadedPIREP = None
-
-    @property
-    def mainWindow(self):
-        """Get the main window of the GUI."""
-        return self._mainWindow
-        
-    @property
-    def logger(self):
-        """Get the logger used by us."""
-        return self._logger
-        
-    @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
-
-    @property
-    def loginResult(self):
-        """Get the result of the login."""
-        return self._wizard.loginResult
-
-    @property
-    def bookedFlight(self):
-        """Get the booked flight selected, if any."""
-        return self._wizard.bookedFlight
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight."""
-        return self._wizard.cargoWeight
-
-    @property
-    def zfw(self):
-        """Get Zero-Fuel Weight calculated for the current flight."""
-        return self._wizard.zfw
-        
-    @property
-    def filedCruiseAltitude(self):
-        """Get cruise altitude filed for the current flight."""
-        return self._wizard.filedCruiseAltitude
-        
-    @property
-    def cruiseAltitude(self):
-        """Get cruise altitude set for the current flight."""
-        return self._wizard.cruiseAltitude
-
-    @property
-    def route(self):
-        """Get the flight route."""
-        return self._wizard.route
-
-    @property
-    def departureMETAR(self):
-        """Get the METAR of the deprature airport."""
-        return self._wizard.departureMETAR
-        
-    @property
-    def arrivalMETAR(self):
-        """Get the METAR of the deprature airport."""
-        return self._wizard.arrivalMETAR
-
-    @property
-    def departureRunway(self):
-        """Get the name of the departure runway."""
-        return self._wizard.departureRunway
-        
-    @property
-    def sid(self):
-        """Get the SID."""
-        return self._wizard.sid
-
-    @property
-    def v1(self):
-        """Get the V1 speed calculated for the flight."""
-        return self._wizard.v1
-        
-    @property
-    def vr(self):
-        """Get the Vr speed calculated for the flight."""
-        return self._wizard.vr
-        
-    @property
-    def v2(self):
-        """Get the V2 speed calculated for the flight."""
-        return self._wizard.v2
-        
-    @property
-    def arrivalRunway(self):
-        """Get the arrival runway."""
-        return self._wizard.arrivalRunway
-
-    @property
-    def star(self):
-        """Get the STAR."""
-        return self._wizard.star
-
-    @property
-    def transition(self):
-        """Get the transition."""
-        return self._wizard.transition
-
-    @property
-    def approachType(self):
-        """Get the approach type."""
-        return self._wizard.approachType
-
-    @property
-    def vref(self):
-        """Get the Vref speed calculated for the flight."""
-        return self._wizard.vref
-        
-    @property
-    def flightType(self):
-        """Get the flight type."""
-        return self._wizard.flightType
-
-    @property
-    def online(self):
-        """Get whether the flight was online or not."""
-        return self._wizard.online
-
-    @property
-    def comments(self):
-        """Get the comments."""
-        return self._flightInfo.comments
-
-    @property
-    def flightDefects(self):
-        """Get the flight defects."""
-        return self._flightInfo.flightDefects
-
-    @property
-    def delayCodes(self):
-        """Get the delay codes."""
-        return self._flightInfo.delayCodes
-
-    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()
-
-        self._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,))
-        fs.sendMessage(const.MESSAGETYPE_INFORMATION,
-                       "Welcome to MAVA Logger X " + const.VERSION)
-        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(parent = self._mainWindow,
-                                   type = MESSAGETYPE_ERROR,
-                                   message_format = xstr("conn_failed"))
-    
-        dialog.set_title(WINDOW_TITLE_BASE)
-        dialog.format_secondary_markup(xstr("conn_failed_sec"))
-        
-        dialog.add_button(xstr("button_cancel"), 0)
-        dialog.add_button(xstr("button_tryagain"), 1)
-        dialog.set_default_response(1)
-        
-        result = dialog.run()
-        dialog.hide()
-        if result == 1:
-            self.beginBusy(xstr("connect_busy"))
-            self._simulator.reconnect()
-        else:
-            self.reset()
-        
-    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 = xstr("conn_broken"),
-                                   parent = self._mainWindow)
-        dialog.set_title(WINDOW_TITLE_BASE)
-        dialog.format_secondary_markup(xstr("conn_broken_sec"))
-
-        dialog.add_button(xstr("button_cancel"), 0)
-        dialog.add_button(xstr("button_reconnect"), 1)
-        dialog.set_default_response(1)
-
-        result = dialog.run()
-        dialog.hide()
-        if result == 1:
-            self.beginBusy(xstr("connect_busy"))
-            self._reconnecting = True
-            self._simulator.reconnect()
-        else:
-            self.reset()
-
-    def enableFlightInfo(self):
-        """Enable the flight info tab."""
-        self._flightInfo.enable()
-
-    def reset(self):
-        """Reset the GUI."""
-        self._disconnect()
-
-        self._flightInfo.reset()
-        self._flightInfo.disable()
-        self.resetFlightStatus()
-
-        self._weightHelp.reset()
-        self._weightHelp.disable()
-        self._wizard.reset()
-        self._notebook.set_current_page(0)
-
-        self._logView.get_buffer().set_text("")
-
-    def _disconnect(self, closingMessage = None, duration = 3):
-        """Disconnect from the simulator if connected."""
-        self.stopMonitoring()
-
-        if self._connected:
-            if closingMessage is None:
-                self._flight.simulator.disconnect()
-            else:
-                fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
-                               closingMessage, duration,
-                               disconnect = True)
-            self._connected = False
-
-        self._connecting = False
-        self._reconnecting = False
-        self._statusbar.updateConnection(False, False)
-        self._weightHelp.disable()
-
-        return True
-            
-    def addFlightLogLine(self, timeStr, line):
-        """Write the given message line to the log."""
-        gobject.idle_add(self._writeLog,
-                         GUI._formatFlightLogLine(timeStr, line),
-                         self._logView)
-
-    def updateFlightLogLine(self, index, timeStr, line):
-        """Update the line with the given index."""
-        gobject.idle_add(self._updateFlightLogLine, index,
-                         GUI._formatFlightLogLine(timeStr, line))
-
-    def _updateFlightLogLine(self, index, line):
-        """Replace the contents of the given line in the log."""
-        buffer = self._logView.get_buffer()
-        startIter = buffer.get_iter_at_line(index)
-        endIter = buffer.get_iter_at_line(index + 1)
-        buffer.delete(startIter, endIter)
-        buffer.insert(startIter, line)
-        self._logView.scroll_mark_onscreen(buffer.get_insert())
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Update the data."""
-        gobject.idle_add(self._monitorWindow.setData, state)
-        gobject.idle_add(self._statusbar.updateTime, state.timestamp)
-
-    def resetFlightStatus(self):
-        """Reset the status of the flight."""
-        self._statusbar.resetFlightStatus()
-        self._statusbar.updateTime()
-        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)
-        self._wizard.setStage(stage)
-        if stage==const.STAGE_END:
-            self._disconnect(closingMessage =
-                             "Flight plan closed. Welcome to %s" % \
-                             (self.bookedFlight.arrivalICAO,),
-                             duration = 5)
-
-    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 self.config.hideMinimizedWindow and \
-           (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()
-        if self._showMonitorMenuItem.get_active():
-            self._selfToggling = True
-            self._showMonitorMenuItem.set_active(False)
-        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()
-        if not self._showMonitorMenuItem.get_active():
-            self._selfToggling = True
-            self._showMonitorMenuItem.set_active(True)
-
-    def _toggleMonitorWindow(self, menuItem):
-        if self._selfToggling:
-            self._selfToggling = False
-        elif self._monitorWindow.get_visible():
-            self.hideMonitorWindow()
-        else:
-            self.showMonitorWindow()
-
-    def restart(self):
-        """Quit and restart the application."""
-        self.toRestart = True
-        self._quit(force = True)
-
-    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._wizard.set_sensitive(False)
-        self._weightHelp.set_sensitive(False)
-        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._weightHelp.set_sensitive(True)
-        self._wizard.set_sensitive(True)
-        self._statusbar.updateBusyState(None)
-
-    def initializeWeightHelp(self):
-        """Initialize the weight help tab."""
-        self._weightHelp.reset()
-        self._weightHelp.enable()
-
-    def getFleetAsync(self, callback = None, force = None):
-        """Get the fleet asynchronously."""
-        gobject.idle_add(self.getFleet, callback, force)
-
-    def getFleet(self, callback = None, force = False):
-        """Get the fleet.
-
-        If force is False, and we already have a fleet retrieved,
-        that one will be used."""
-        if self._fleet is None or force:
-            self._fleetCallback = callback
-            self.beginBusy(xstr("fleet_busy"))
-            self.webHandler.getFleet(self._fleetResultCallback)
-        else:
-            callback(self._fleet)
-
-    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.endBusy()
-        if returned:
-            self._fleet = result.fleet
-        else:
-            self._fleet = None
-
-            dialog = gtk.MessageDialog(parent = self.mainWindow,
-                                       type = MESSAGETYPE_ERROR,
-                                       message_format = xstr("fleet_failed"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            dialog.run()
-            dialog.hide()
-
-        callback = self._fleetCallback
-        self._fleetCallback = None
-        if  callback is not None:
-            callback(self._fleet)
-        self._fleetGateStatus.handleFleet(self._fleet)
-
-    def updatePlane(self, tailNumber, status,
-                    gateNumber = None, callback = None):
-        """Update the status of the given plane."""
-        self.beginBusy(xstr("fleet_update_busy"))
-
-        self._updatePlaneCallback = callback
-
-        self._updatePlaneTailNumber = tailNumber
-        self._updatePlaneStatus = status
-        self._updatePlaneGateNumber = gateNumber
-        
-        self.webHandler.updatePlane(self._updatePlaneResultCallback,
-                                    tailNumber, status, gateNumber)
-
-    def _updatePlaneResultCallback(self, returned, result):
-        """Called when the status of a plane has been updated."""
-        gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
-
-    def _handleUpdatePlaneResult(self, returned, result):
-        """Handle the plane update result."""
-        self.endBusy()
-        if returned:
-            success = result.success
-            if success:
-                if self._fleet is not None:
-                    self._fleet.updatePlane(self._updatePlaneTailNumber,
-                                            self._updatePlaneStatus,
-                                            self._updatePlaneGateNumber)
-                    self._fleetGateStatus.handleFleet(self._fleet)
-        else:
-            dialog = gtk.MessageDialog(parent = self.mainWindow,
-                                       type = MESSAGETYPE_ERROR,
-                                       message_format = xstr("fleet_update_failed"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            dialog.run()
-            dialog.hide()
-
-            success = None
-
-        callback = self._updatePlaneCallback
-        self._updatePlaneCallback = None
-        if callback is not None:
-            callback(success)
-
-    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:
-            #print >> sys.__stdout__, line
-            self._writeLog(line + "\n", self._debugLogView)
-
-        if text:
-            #print >> sys.__stdout__, text,
-            self._writeLog(text, self._debugLogView)
-
-    def connectSimulator(self, aircraftType):
-        """Connect to the simulator for the first time."""
-        self._logger.reset()
-
-        self._flight = flight.Flight(self._logger, self)
-        self._flight.flareTimeFromFS = self.config.flareTimeFromFS
-        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)
-            fs.setupMessageSending(self.config, self._simulator)
-            self._setupTimeSync()
-        
-        self._flight.simulator = self._simulator
-
-        self.beginBusy(xstr("connect_busy"))
-        self._statusbar.updateConnection(self._connecting, self._connected)
-
-        self._connecting = True
-        self._simulator.connect(self._flight.aircraft)        
-
-    def startMonitoring(self):
-        """Start monitoring."""
-        if not self._monitoring:
-            self.simulator.startMonitoring()
-            self._monitoring = True
-
-    def stopMonitoring(self):
-        """Stop monitoring."""
-        if self._monitoring:
-            self.simulator.stopMonitoring()
-            self._monitoring = False
-
-    def _buildMenuBar(self, accelGroup):
-        """Build the main menu bar."""
-        menuBar = gtk.MenuBar()
-        
-        fileMenuItem = gtk.MenuItem(xstr("menu_file"))
-        fileMenu = gtk.Menu()
-        fileMenuItem.set_submenu(fileMenu)
-        menuBar.append(fileMenuItem)
-
-        loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
-        loadPIREPMenuItem.set_use_stock(True)
-        loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
-        loadPIREPMenuItem.add_accelerator("activate", accelGroup,
-                                          ord(xstr("menu_file_loadPIREP_key")),
-                                          CONTROL_MASK, ACCEL_VISIBLE)
-        loadPIREPMenuItem.connect("activate", self._loadPIREP)
-        fileMenu.append(loadPIREPMenuItem)
-
-        fileMenu.append(gtk.SeparatorMenuItem())
-
-        quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
-        quitMenuItem.set_use_stock(True)
-        quitMenuItem.set_label(xstr("menu_file_quit"))
-        quitMenuItem.add_accelerator("activate", accelGroup,
-                                     ord(xstr("menu_file_quit_key")),
-                                     CONTROL_MASK, ACCEL_VISIBLE)
-        quitMenuItem.connect("activate", self._quit)
-        fileMenu.append(quitMenuItem)
-
-        toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
-        toolsMenu = gtk.Menu()
-        toolsMenuItem.set_submenu(toolsMenu)
-        menuBar.append(toolsMenuItem)
-
-        prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
-        prefsMenuItem.set_use_stock(True)
-        prefsMenuItem.set_label(xstr("menu_tools_prefs"))
-        prefsMenuItem.add_accelerator("activate", accelGroup,
-                                      ord(xstr("menu_tools_prefs_key")),
-                                      CONTROL_MASK, ACCEL_VISIBLE)
-        prefsMenuItem.connect("activate", self._editPreferences)
-        toolsMenu.append(prefsMenuItem)
-
-        viewMenuItem = gtk.MenuItem(xstr("menu_view"))
-        viewMenu = gtk.Menu()
-        viewMenuItem.set_submenu(viewMenu)
-        menuBar.append(viewMenuItem)
-
-        self._showMonitorMenuItem = gtk.CheckMenuItem()
-        self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
-        self._showMonitorMenuItem.set_use_underline(True)
-        self._showMonitorMenuItem.set_active(False)
-        self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
-                                                  ord(xstr("menu_view_monitor_key")),
-                                                  CONTROL_MASK, ACCEL_VISIBLE)
-        self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
-        viewMenu.append(self._showMonitorMenuItem)
-
-        showDebugMenuItem = gtk.CheckMenuItem()
-        showDebugMenuItem.set_label(xstr("menu_view_debug"))
-        showDebugMenuItem.set_use_underline(True)
-        showDebugMenuItem.set_active(False)
-        showDebugMenuItem.add_accelerator("activate", accelGroup,
-                                          ord(xstr("menu_view_debug_key")),
-                                          CONTROL_MASK, ACCEL_VISIBLE)
-        showDebugMenuItem.connect("toggled", self._toggleDebugLog)
-        viewMenu.append(showDebugMenuItem)
-
-        return menuBar
-
-    def _toggleDebugLog(self, menuItem):
-        """Toggle the debug log."""
-        if menuItem.get_active():
-            label = gtk.Label(xstr("tab_debug_log"))
-            label.set_use_underline(True)
-            label.set_tooltip_text(xstr("tab_debug_log_tooltip"))        
-            self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
-            self._notebook.set_current_page(self._debugLogPage)
-        else:
-            self._notebook.remove_page(self._debugLogPage)
-
-    def _buildLogWidget(self):
-        """Build the widget for the log."""
-        alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        alignment.set_padding(padding_top = 8, padding_bottom = 8,
-                              padding_left = 16, padding_right = 16)
-
-        logScroller = gtk.ScrolledWindow()
-        # FIXME: these should be constants in common
-        logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                               else gtk.POLICY_AUTOMATIC,
-                               gtk.PolicyType.AUTOMATIC if pygobject
-                               else gtk.POLICY_AUTOMATIC)
-        logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                    else gtk.SHADOW_IN)
-        logView = gtk.TextView()
-        logView.set_editable(False)
-        logScroller.add(logView)
-
-        logBox = gtk.VBox()
-        logBox.pack_start(logScroller, True, True, 0)
-        logBox.set_size_request(-1, 200)
-
-        alignment.add(logBox)
-
-        return (alignment, logView)
-
-    def _writeLog(self, msg, logView):
-        """Write the given message to the log."""
-        buffer = logView.get_buffer()
-        buffer.insert(buffer.get_end_iter(), msg)
-        logView.scroll_mark_onscreen(buffer.get_insert())
-
-    def _quit(self, what = None, force = False):
-        """Quit from the application."""
-        if force:
-            result=RESPONSETYPE_YES
-        else:
-            dialog = gtk.MessageDialog(parent = self._mainWindow,
-                                       type = MESSAGETYPE_QUESTION,
-                                       message_format = xstr("quit_question"))
-
-            dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
-            dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
-
-            dialog.set_title(WINDOW_TITLE_BASE)
-            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)
-
-    def _editPreferences(self, menuItem):
-        """Callback for editing the preferences."""
-        self._preferences.run(self.config)
-        self._setupTimeSync()
-
-    def _setupTimeSync(self):
-        """Enable or disable the simulator time synchronization based on the
-        configuration."""
-        simulator = self._simulator
-        if simulator is not None:
-            if self.config.syncFSTime:
-                simulator.enableTimeSync()
-            else:
-                simulator.disableTimeSync()
-
-    def _loadPIREP(self, menuItem):
-        """Load a PIREP for sending."""
-        dialog = self._getLoadPirepDialog()
-
-        if self._lastLoadedPIREP:
-            dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
-        else:
-            pirepDirectory = self.config.pirepDirectory
-            if pirepDirectory is not None:
-                dialog.set_current_folder(pirepDirectory)
-        
-        result = dialog.run()
-        dialog.hide()
-
-        if result==RESPONSETYPE_OK:
-            self._lastLoadedPIREP = dialog.get_filename()
-
-            pirep = PIREP.load(self._lastLoadedPIREP)
-            if pirep is None:
-                dialog = gtk.MessageDialog(parent = self._mainWindow,
-                                           type = MESSAGETYPE_ERROR,
-                                           message_format = xstr("loadPIREP_failed"))
-                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-                dialog.set_title(WINDOW_TITLE_BASE)
-                dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
-                dialog.run()
-                dialog.hide()
-            else:
-                dialog = self._getSendLoadedDialog(pirep)
-                dialog.show_all()
-                result = dialog.run()
-                dialog.hide()
-
-                if result==RESPONSETYPE_OK:
-                    self.sendPIREP(pirep)
-
-    def _getLoadPirepDialog(self):
-        """Get the PIREP loading file chooser dialog.
-
-        If it is not created yet, it will be created."""
-        if self._loadPIREPDialog is None:
-            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                           xstr("loadPIREP_browser_title"),
-                                           action = FILE_CHOOSER_ACTION_OPEN,
-                                           buttons = (gtk.STOCK_CANCEL,
-                                                      RESPONSETYPE_CANCEL,
-                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
-                                           parent = self._mainWindow)
-            dialog.set_modal(True)
-            
-
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("loadPIREP_filter_pireps"))
-            filter.add_pattern("*.pirep")
-            dialog.add_filter(filter)
-            
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("loadPIREP_filter_all"))
-            filter.add_pattern("*.*")
-            dialog.add_filter(filter)
-
-            self._loadPIREPDialog = dialog
-
-        return self._loadPIREPDialog
-
-    def _getSendLoadedDialog(self, pirep):
-        """Get a dialog displaying the main information of the flight from the
-        PIREP and providing Cancel and Send buttons."""
-        dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
-                            xstr("loadPIREP_send_title"),
-                            parent = self._mainWindow,
-                            flags = DIALOG_MODAL)
-
-        contentArea = dialog.get_content_area()
-
-        label = gtk.Label(xstr("loadPIREP_send_help"))
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.set_padding(padding_top = 16, padding_bottom = 0,
-                              padding_left = 48, padding_right = 48)
-        alignment.add(label)
-        contentArea.pack_start(alignment, False, False, 8)
-
-        table = gtk.Table(5, 2)
-        tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
-                                   padding_left = 48, padding_right = 48)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        tableAlignment.add(table)
-        contentArea.pack_start(tableAlignment, True, True, 8)
-
-        bookedFlight = pirep.bookedFlight
-
-        label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
-        label.set_use_markup(True)
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 0, 1)
-        
-        label = gtk.Label(bookedFlight.callsign)
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 1, 2, 0, 1)
-
-        label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
-        label.set_use_markup(True)
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 1, 2)
-        
-        label = gtk.Label(str(bookedFlight.departureTime.date()))
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 1, 2, 1, 2)
-
-        label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
-        label.set_use_markup(True)
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 2, 3)
-        
-        label = gtk.Label(bookedFlight.departureICAO)
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 1, 2, 2, 3)
-
-        label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
-        label.set_use_markup(True)
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 3, 4)
-        
-        label = gtk.Label(bookedFlight.arrivalICAO)
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 1, 2, 3, 4)
-
-        label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
-        label.set_use_markup(True)
-        labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, 4, 5)
-
-        rating = pirep.rating
-        label = gtk.Label()
-        if rating<0:
-            label.set_markup('<b><span foreground="red">NO GO</span></b>')
-        else:
-            label.set_text("%.1f %%" % (rating,))
-        
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 1, 2, 4, 5)
-
-        dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
-        dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
-        
-        return dialog
-                            
-    def sendPIREP(self, pirep, callback = None):
-        """Send the given PIREP."""
-        self.beginBusy(xstr("sendPIREP_busy"))
-        self._sendPIREPCallback = callback
-        self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
-
-    def _pirepSentCallback(self, returned, result):
-        """Callback for the PIREP sending result."""
-        gobject.idle_add(self._handlePIREPSent, returned, result)
-
-    def _handlePIREPSent(self, returned, result):
-        """Callback for the PIREP sending result."""
-        self.endBusy()
-        secondaryMarkup = None
-        type = MESSAGETYPE_ERROR
-        if returned:
-            if result.success:
-                type = MESSAGETYPE_INFO
-                messageFormat = xstr("sendPIREP_success")
-                secondaryMarkup = xstr("sendPIREP_success_sec")
-            elif result.alreadyFlown:
-                messageFormat = xstr("sendPIREP_already")
-                secondaryMarkup = xstr("sendPIREP_already_sec")
-            elif result.notAvailable:
-                messageFormat = xstr("sendPIREP_notavail")
-            else:
-                messageFormat = xstr("sendPIREP_unknown")
-                secondaryMarkup = xstr("sendPIREP_unknown_sec")
-        else:
-            print "PIREP sending failed", result
-            messageFormat = xstr("sendPIREP_failed")
-            secondaryMarkup = xstr("sendPIREP_failed_sec")
-        
-        dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
-                                   type = type, message_format = messageFormat)
-        dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-        dialog.set_title(WINDOW_TITLE_BASE)
-        if secondaryMarkup is not None:
-            dialog.format_secondary_markup(secondaryMarkup)
-
-        dialog.run()
-        dialog.hide()
-
-        callback = self._sendPIREPCallback
-        self._sendPIREPCallback = None
-        if callback is not None:
-            callback(returned, result)
Index: src/mlx/gui/info.py
===================================================================
--- src/mlx/gui/info.py	(revision 128)
+++ src/mlx/gui/info.py	(revision 128)
@@ -0,0 +1,158 @@
+#The Flight Info tab
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+
+#------------------------------------------------------------------------------
+
+class FlightInfo(gtk.VBox):
+    """The flight info tab."""
+    @staticmethod
+    def _delayCodes():
+        """Get an array of delay codes."""
+        return [ (const.DELAYCODE_LOADING, xstr("info_delay_loading")),
+                 (const.DELAYCODE_VATSIM, xstr("info_delay_vatsim")),
+                 (const.DELAYCODE_NETWORK, xstr("info_delay_net")),
+                 (const.DELAYCODE_CONTROLLER, xstr("info_delay_atc")),
+                 (const.DELAYCODE_SYSTEM, xstr("info_delay_system")),
+                 (const.DELAYCODE_NAVIGATION, xstr("info_delay_nav")),
+                 (const.DELAYCODE_TRAFFIC, xstr("info_delay_traffic")),
+                 (const.DELAYCODE_APRON, xstr("info_delay_apron")),
+                 (const.DELAYCODE_WEATHER, xstr("info_delay_weather")),
+                 (const.DELAYCODE_PERSONAL, xstr("info_delay_personal")) ]
+    
+    @staticmethod
+    def _createCommentArea(label):
+        """Create a comment area.
+
+        Returns a tuple of two items:
+        - the top-level widget of the comment area, and
+        - the comment text editor."""
+
+        frame = gtk.Frame(label = label)
+        label = frame.get_label_widget()
+        label.set_use_underline(True)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 8, padding_right = 8)
+        
+        scroller = gtk.ScrolledWindow()
+        # FIXME: these should be constants
+        scroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                            else gtk.POLICY_AUTOMATIC,
+                            gtk.PolicyType.AUTOMATIC if pygobject
+                            else gtk.POLICY_AUTOMATIC)
+        scroller.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                 else gtk.SHADOW_IN)
+        comments = gtk.TextView()
+        comments.set_wrap_mode(WRAP_WORD)
+        scroller.add(comments)
+        alignment.add(scroller)
+        frame.add(alignment)
+
+        label.set_mnemonic_widget(comments)
+
+        return (frame, comments)
+
+    def __init__(self, gui):
+        """Construct the flight info tab."""
+        super(FlightInfo, self).__init__()
+        self._gui = gui
+
+        self._commentsAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                                xscale = 1.0, yscale = 1.0)
+        commentsBox = gtk.HBox()
+
+        (frame, self._comments) = FlightInfo._createCommentArea(xstr("info_comments"))
+        commentsBox.pack_start(frame, True, True, 8)
+
+        (frame, self._flightDefects) = \
+             FlightInfo._createCommentArea(xstr("info_defects"))
+        commentsBox.pack_start(frame, True, True, 8)
+
+        self._commentsAlignment.add(commentsBox)
+        self.pack_start(self._commentsAlignment, True, True, 8)
+
+        frame = gtk.Frame(label = xstr("info_delay"))
+        label = frame.get_label_widget()
+        label.set_use_underline(True)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 8, padding_right = 8)
+
+        self._delayTable = table = gtk.Table(5, 2)
+        table.set_col_spacings(16)
+
+        row = 0
+        column = 0
+
+        self._delayCodeWidgets = []
+        for (_code, label) in FlightInfo._delayCodes():
+            button = gtk.CheckButton(label)
+            button.set_use_underline(True)
+            table.attach(button, column, column + 1, row, row + 1)
+            self._delayCodeWidgets.append(button)
+            if column==0:
+                column += 1
+            else:
+                row += 1
+                column = 0
+
+        alignment.add(table)
+        frame.add(alignment)
+
+        self._delayAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                             xscale = 0.0, yscale = 0.0)
+        self._delayAlignment.add(frame)
+
+        self.pack_start(self._delayAlignment, False, False, 8)
+
+    @property
+    def comments(self):
+        """Get the comments."""
+        buffer = self._comments.get_buffer()
+        return text2unicode(buffer.get_text(buffer.get_start_iter(),
+                                            buffer.get_end_iter(), True))
+    
+    @property
+    def flightDefects(self):
+        """Get the flight defects."""
+        buffer = self._flightDefects.get_buffer()
+        return text2unicode(buffer.get_text(buffer.get_start_iter(),
+                                            buffer.get_end_iter(), True))
+
+    @property
+    def delayCodes(self):
+        """Get the list of delay codes checked by the user."""
+        codes =  []
+        delayCodes = FlightInfo._delayCodes()
+        for index in range(0, len(delayCodes)):
+            if self._delayCodeWidgets[index].get_active():
+                codes.append(delayCodes[index][0])
+        return codes
+            
+    def enable(self):
+        """Enable the flight info tab."""
+        self._comments.set_sensitive(True)
+        self._flightDefects.set_sensitive(True)
+        self._delayTable.set_sensitive(True)
+        
+    def disable(self):
+        """Enable the flight info tab."""
+        self._comments.set_sensitive(False)
+        self._flightDefects.set_sensitive(False)
+        self._delayTable.set_sensitive(False)
+
+    def reset(self):
+        """Reset the flight info tab."""
+        self._comments.get_buffer().set_text("")
+        self._flightDefects.get_buffer().set_text("")
+
+        for widget in self._delayCodeWidgets:
+            widget.set_active(False)
Index: src/mlx/gui/info.py
===================================================================
--- src/mlx/gui/info.py	(revision 128)
+++ 	(revision )
@@ -1,158 +1,0 @@
-#The Flight Info tab
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-
-#------------------------------------------------------------------------------
-
-class FlightInfo(gtk.VBox):
-    """The flight info tab."""
-    @staticmethod
-    def _delayCodes():
-        """Get an array of delay codes."""
-        return [ (const.DELAYCODE_LOADING, xstr("info_delay_loading")),
-                 (const.DELAYCODE_VATSIM, xstr("info_delay_vatsim")),
-                 (const.DELAYCODE_NETWORK, xstr("info_delay_net")),
-                 (const.DELAYCODE_CONTROLLER, xstr("info_delay_atc")),
-                 (const.DELAYCODE_SYSTEM, xstr("info_delay_system")),
-                 (const.DELAYCODE_NAVIGATION, xstr("info_delay_nav")),
-                 (const.DELAYCODE_TRAFFIC, xstr("info_delay_traffic")),
-                 (const.DELAYCODE_APRON, xstr("info_delay_apron")),
-                 (const.DELAYCODE_WEATHER, xstr("info_delay_weather")),
-                 (const.DELAYCODE_PERSONAL, xstr("info_delay_personal")) ]
-    
-    @staticmethod
-    def _createCommentArea(label):
-        """Create a comment area.
-
-        Returns a tuple of two items:
-        - the top-level widget of the comment area, and
-        - the comment text editor."""
-
-        frame = gtk.Frame(label = label)
-        label = frame.get_label_widget()
-        label.set_use_underline(True)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 8, padding_right = 8)
-        
-        scroller = gtk.ScrolledWindow()
-        # FIXME: these should be constants
-        scroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                            else gtk.POLICY_AUTOMATIC,
-                            gtk.PolicyType.AUTOMATIC if pygobject
-                            else gtk.POLICY_AUTOMATIC)
-        scroller.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                 else gtk.SHADOW_IN)
-        comments = gtk.TextView()
-        comments.set_wrap_mode(WRAP_WORD)
-        scroller.add(comments)
-        alignment.add(scroller)
-        frame.add(alignment)
-
-        label.set_mnemonic_widget(comments)
-
-        return (frame, comments)
-
-    def __init__(self, gui):
-        """Construct the flight info tab."""
-        super(FlightInfo, self).__init__()
-        self._gui = gui
-
-        self._commentsAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                                xscale = 1.0, yscale = 1.0)
-        commentsBox = gtk.HBox()
-
-        (frame, self._comments) = FlightInfo._createCommentArea(xstr("info_comments"))
-        commentsBox.pack_start(frame, True, True, 8)
-
-        (frame, self._flightDefects) = \
-             FlightInfo._createCommentArea(xstr("info_defects"))
-        commentsBox.pack_start(frame, True, True, 8)
-
-        self._commentsAlignment.add(commentsBox)
-        self.pack_start(self._commentsAlignment, True, True, 8)
-
-        frame = gtk.Frame(label = xstr("info_delay"))
-        label = frame.get_label_widget()
-        label.set_use_underline(True)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 8, padding_right = 8)
-
-        self._delayTable = table = gtk.Table(5, 2)
-        table.set_col_spacings(16)
-
-        row = 0
-        column = 0
-
-        self._delayCodeWidgets = []
-        for (_code, label) in FlightInfo._delayCodes():
-            button = gtk.CheckButton(label)
-            button.set_use_underline(True)
-            table.attach(button, column, column + 1, row, row + 1)
-            self._delayCodeWidgets.append(button)
-            if column==0:
-                column += 1
-            else:
-                row += 1
-                column = 0
-
-        alignment.add(table)
-        frame.add(alignment)
-
-        self._delayAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                             xscale = 0.0, yscale = 0.0)
-        self._delayAlignment.add(frame)
-
-        self.pack_start(self._delayAlignment, False, False, 8)
-
-    @property
-    def comments(self):
-        """Get the comments."""
-        buffer = self._comments.get_buffer()
-        return text2unicode(buffer.get_text(buffer.get_start_iter(),
-                                            buffer.get_end_iter(), True))
-    
-    @property
-    def flightDefects(self):
-        """Get the flight defects."""
-        buffer = self._flightDefects.get_buffer()
-        return text2unicode(buffer.get_text(buffer.get_start_iter(),
-                                            buffer.get_end_iter(), True))
-
-    @property
-    def delayCodes(self):
-        """Get the list of delay codes checked by the user."""
-        codes =  []
-        delayCodes = FlightInfo._delayCodes()
-        for index in range(0, len(delayCodes)):
-            if self._delayCodeWidgets[index].get_active():
-                codes.append(delayCodes[index][0])
-        return codes
-            
-    def enable(self):
-        """Enable the flight info tab."""
-        self._comments.set_sensitive(True)
-        self._flightDefects.set_sensitive(True)
-        self._delayTable.set_sensitive(True)
-        
-    def disable(self):
-        """Enable the flight info tab."""
-        self._comments.set_sensitive(False)
-        self._flightDefects.set_sensitive(False)
-        self._delayTable.set_sensitive(False)
-
-    def reset(self):
-        """Reset the flight info tab."""
-        self._comments.get_buffer().set_text("")
-        self._flightDefects.get_buffer().set_text("")
-
-        for widget in self._delayCodeWidgets:
-            widget.set_active(False)
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 89)
+++ src/mlx/gui/monitor.py	(revision 89)
@@ -0,0 +1,315 @@
+# The monitoring window
+
+from mlx.gui.common import *
+
+import mlx.const as const
+import mlx.util as util
+
+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)
+
+        (label, self._position) = self._createLabeledEntry("Position:", 25)
+        table.attach(label, 6, 7, 6, 7)
+        table.attach(self._position, 7, 10, 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("-")
+            self._position.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,))
+            self._position.set_text(util.getCoordinateString((aircraftState.latitude,
+                                                              aircraftState.longitude)))
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 89)
+++ 	(revision )
@@ -1,315 +1,0 @@
-# The monitoring window
-
-from mlx.gui.common import *
-
-import mlx.const as const
-import mlx.util as util
-
-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)
-
-        (label, self._position) = self._createLabeledEntry("Position:", 25)
-        table.attach(label, 6, 7, 6, 7)
-        table.attach(self._position, 7, 10, 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("-")
-            self._position.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,))
-            self._position.set_text(util.getCoordinateString((aircraftState.latitude,
-                                                              aircraftState.longitude)))
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/prefs.py
===================================================================
--- src/mlx/gui/prefs.py	(revision 164)
+++ src/mlx/gui/prefs.py	(revision 164)
@@ -0,0 +1,394 @@
+# Module for the preferences dialog
+
+#------------------------------------------------------------------------------
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+
+import urlparse
+
+#------------------------------------------------------------------------------
+
+class Preferences(gtk.Dialog):
+    """The preferences dialog."""
+    def __init__(self, gui):
+        """Construct the dialog."""
+        super(Preferences, self).__init__(WINDOW_TITLE_BASE + " " +
+                                          xstr("prefs_title"),
+                                          gui.mainWindow,
+                                          DIALOG_MODAL)
+
+        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
+        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
+        
+        self._gui = gui
+
+        contentArea = self.get_content_area()
+
+        notebook = gtk.Notebook()
+        contentArea.pack_start(notebook, True, True, 4)
+
+        general = self._buildGeneral()
+        label = gtk.Label(xstr("prefs_tab_general"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("prefs_tab_general_tooltip"))
+        notebook.append_page(general, label)
+
+        messages = self._buildMessages()
+        label = gtk.Label(xstr("prefs_tab_messages"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("prefs_tab_message_tooltip"))
+        notebook.append_page(messages, label)
+
+        advanced = self._buildAdvanced()
+        label = gtk.Label(xstr("prefs_tab_advanced"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("prefs_tab_advanced_tooltip"))
+        notebook.append_page(advanced, label)
+
+    def run(self, config):
+        """Run the preferences dialog.
+
+        The dialog will be set up from data in the given configuration. If the
+        changes are accepted by the user, the configuration is updated and saved."""
+        self._fromConfig(config)
+
+        self.show_all()
+        response = super(Preferences, self).run()
+        self.hide()
+
+        if response==RESPONSETYPE_ACCEPT:
+            self._toConfig(config)
+            config.save()
+
+    def _fromConfig(self, config):
+        """Setup the dialog from the given configuration."""
+        self._setLanguage(config.language)
+        self._hideMinimizedWindow.set_active(config.hideMinimizedWindow)
+        self._onlineGateSystem.set_active(config.onlineGateSystem)
+        self._onlineACARS.set_active(config.onlineACARS)
+        self._flareTimeFromFS.set_active(config.flareTimeFromFS)
+        self._syncFSTime.set_active(config.syncFSTime)
+
+        pirepDirectory = config.pirepDirectory
+        self._pirepDirectory.set_text("" if pirepDirectory is None
+                                      else pirepDirectory)
+
+        for messageType in const.messageTypes:
+            level = config.getMessageTypeLevel(messageType)
+            button = self._msgFSCheckButtons[messageType]
+            button.set_active(level == const.MESSAGELEVEL_FS or
+                              level == const.MESSAGELEVEL_BOTH)
+            button = self._msgSoundCheckButtons[messageType]
+            button.set_active(level == const.MESSAGELEVEL_SOUND or
+                              level == const.MESSAGELEVEL_BOTH)
+
+        self._togglingAutoUpdate = True
+        self._autoUpdate.set_active(config.autoUpdate)
+        self._togglingAutoUpdate = False
+        if not config.autoUpdate:
+            self._warnedAutoUpdate = True
+
+        self._updateURL.set_text(config.updateURL)
+
+    def _toConfig(self, config):
+        """Setup the given config from the settings in the dialog."""
+        config.language = self._getLanguage()
+        config.hideMinimizedWindow = self._hideMinimizedWindow.get_active()
+        config.onlineGateSystem = self._onlineGateSystem.get_active()
+        config.onlineACARS = self._onlineACARS.get_active()
+        config.flareTimeFromFS = self._flareTimeFromFS.get_active()
+        config.syncFSTime = self._syncFSTime.get_active()
+        config.pirepDirectory = text2unicode(self._pirepDirectory.get_text())
+
+        for messageType in const.messageTypes:
+            fsButtonActive = self._msgFSCheckButtons[messageType].get_active()
+            soundButtonActive = self._msgSoundCheckButtons[messageType].get_active()
+            if fsButtonActive:
+                level = const.MESSAGELEVEL_BOTH if soundButtonActive \
+                        else const.MESSAGELEVEL_FS
+            elif soundButtonActive:
+                level = const.MESSAGELEVEL_SOUND
+            else:
+                level = const.MESSAGELEVEL_NONE
+            config.setMessageTypeLevel(messageType, level)
+
+        config.autoUpdate = self._autoUpdate.get_active()
+        config.updateURL = self._updateURL.get_text()
+
+    def _buildGeneral(self):
+        """Build the page for the general settings."""
+        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                      xscale = 1.0, yscale = 0.0)
+        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
+                                  padding_left = 4, padding_right = 4)
+        mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        languageBox = gtk.HBox()
+        mainBox.pack_start(languageBox, False, False, 4)
+
+        label = gtk.Label(xstr("prefs_language"))
+        label.set_use_underline(True)
+
+        languageBox.pack_start(label, False, False, 4)
+
+        self._languageList = gtk.ListStore(str, str)
+        for language in const.languages:
+            self._languageList.append([xstr("prefs_lang_" + language),
+                                       language])
+
+        self._languageComboBox = languageComboBox = \
+            gtk.ComboBox(model = self._languageList)                   
+        cell = gtk.CellRendererText()
+        languageComboBox.pack_start(cell, True)
+        languageComboBox.add_attribute(cell, 'text', 0)
+        languageComboBox.set_tooltip_text(xstr("prefs_language_tooltip"))
+        languageComboBox.connect("changed", self._languageChanged)
+        languageBox.pack_start(languageComboBox, False, False, 4)
+
+        label.set_mnemonic_widget(languageComboBox)
+
+        self._changingLanguage = False
+        self._warnedRestartNeeded = False
+
+        self._hideMinimizedWindow = gtk.CheckButton(xstr("prefs_hideMinimizedWindow"))
+        self._hideMinimizedWindow.set_use_underline(True)
+        self._hideMinimizedWindow.set_tooltip_text(xstr("prefs_hideMinimizedWindow_tooltip"))
+        mainBox.pack_start(self._hideMinimizedWindow, False, False, 4)
+
+        self._onlineGateSystem = gtk.CheckButton(xstr("prefs_onlineGateSystem"))
+        self._onlineGateSystem.set_use_underline(True)
+        self._onlineGateSystem.set_tooltip_text(xstr("prefs_onlineGateSystem_tooltip"))
+        mainBox.pack_start(self._onlineGateSystem, False, False, 4)
+
+        self._onlineACARS = gtk.CheckButton(xstr("prefs_onlineACARS"))
+        self._onlineACARS.set_use_underline(True)
+        self._onlineACARS.set_tooltip_text(xstr("prefs_onlineACARS_tooltip"))
+        mainBox.pack_start(self._onlineACARS, False, False, 4)
+
+        self._flareTimeFromFS = gtk.CheckButton(xstr("prefs_flaretimeFromFS"))
+        self._flareTimeFromFS.set_use_underline(True)
+        self._flareTimeFromFS.set_tooltip_text(xstr("prefs_flaretimeFromFS_tooltip"))
+        mainBox.pack_start(self._flareTimeFromFS, False, False, 4)
+                                       
+        self._syncFSTime = gtk.CheckButton(xstr("prefs_syncFSTime"))
+        self._syncFSTime.set_use_underline(True)
+        self._syncFSTime.set_tooltip_text(xstr("prefs_syncFSTime_tooltip"))
+        mainBox.pack_start(self._syncFSTime, False, False, 4)
+
+        pirepBox = gtk.HBox()
+        mainBox.pack_start(pirepBox, False, False, 4)
+
+        label = gtk.Label(xstr("prefs_pirepDirectory"))
+        label.set_use_underline(True)
+        pirepBox.pack_start(label, False, False, 4)
+
+        self._pirepDirectory = gtk.Entry()
+        self._pirepDirectory.set_tooltip_text(xstr("prefs_pirepDirectory_tooltip"))
+        label.set_mnemonic_widget(self._pirepDirectory)
+        pirepBox.pack_start(self._pirepDirectory, True, True, 4)
+
+        self._pirepDirectoryButton = gtk.Button(xstr("button_browse"))
+        self._pirepDirectoryButton.connect("clicked",
+                                           self._pirepDirectoryButtonClicked)
+        pirepBox.pack_start(self._pirepDirectoryButton, False, False, 4)
+        
+        return mainAlignment
+
+    def _setLanguage(self, language):
+        """Set the language to the given one."""
+        iter = self._languageList.get_iter_first()
+        while iter is not None:
+            (lang,) = self._languageList.get(iter, 1)
+            if (not language and lang=="$system") or \
+               lang==language:
+                self._changingLanguage = True
+                self._languageComboBox.set_active_iter(iter)
+                self._changingLanguage = False
+                break
+            else:
+                iter = self._languageList.iter_next(iter)
+
+    def _getLanguage(self):
+        """Get the language selected by the user."""        
+        iter = self._languageComboBox.get_active_iter()
+        (lang,) = self._languageList.get(iter, 1)
+        return "" if lang=="$system" else lang
+
+    def _languageChanged(self, comboBox):
+        """Called when the language has changed."""
+        if not self._changingLanguage and not self._warnedRestartNeeded:
+            dialog = gtk.MessageDialog(parent = self,
+                                       type = MESSAGETYPE_INFO,
+                                       message_format = xstr("prefs_restart"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(self.get_title())
+            dialog.format_secondary_markup(xstr("prefs_language_restart_sec"))
+            dialog.run()
+            dialog.hide()
+            self._warnedRestartNeeded = True
+       
+    def _pirepDirectoryButtonClicked(self, button):
+        """Called when the PIREP directory button is clicked."""
+        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                       xstr("prefs_pirepDirectory_browser_title"),
+                                       action = FILE_CHOOSER_ACTION_SELECT_FOLDER,
+                                       buttons = (gtk.STOCK_CANCEL, RESPONSETYPE_CANCEL,
+                                                  gtk.STOCK_OK, RESPONSETYPE_OK),
+                                       parent = self)
+        dialog.set_modal(True)
+        dialog.set_transient_for(self)
+
+        directory = self._pirepDirectory.get_text()
+        if directory:
+            dialog.select_filename(directory)
+        
+        result = dialog.run()
+        dialog.hide()
+
+        if result==RESPONSETYPE_OK:
+            self._pirepDirectory.set_text(dialog.get_filename())
+        
+    def _buildMessages(self):
+        """Build the page for the message settings."""
+
+        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                      xscale = 0.0, yscale = 0.0)
+        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
+                                  padding_left = 4, padding_right = 4)
+        mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        table = gtk.Table(len(const.messageTypes) + 1, 3)
+        table.set_row_spacings(8)
+        table.set_col_spacings(32)
+        table.set_homogeneous(False)
+        mainBox.pack_start(table, False, False, 4)
+        
+        label = gtk.Label(xstr("prefs_msgs_fs"))
+        label.set_justify(JUSTIFY_CENTER)
+        label.set_alignment(0.5, 1.0)
+        table.attach(label, 1, 2, 0, 1)
+        
+        label = gtk.Label(xstr("prefs_msgs_sound"))
+        label.set_justify(JUSTIFY_CENTER)
+        label.set_alignment(0.5, 1.0)
+        table.attach(label, 2, 3, 0, 1)
+
+        self._msgFSCheckButtons = {}
+        self._msgSoundCheckButtons = {}        
+        row = 1
+        for messageType in const.messageTypes:
+            messageTypeStr = const.messageType2string(messageType)
+            label = gtk.Label(xstr("prefs_msgs_type_" + messageTypeStr))
+            label.set_justify(JUSTIFY_CENTER)
+            label.set_use_underline(True)
+            label.set_alignment(0.5, 0.5)
+            table.attach(label, 0, 1, row, row+1)
+
+            fsCheckButton = gtk.CheckButton()
+            alignment = gtk.Alignment(xscale = 0.0, yscale = 0.0,
+                                      xalign = 0.5, yalign = 0.5)
+            alignment.add(fsCheckButton)
+            table.attach(alignment, 1, 2, row, row+1)
+            self._msgFSCheckButtons[messageType] = fsCheckButton
+            
+            soundCheckButton = gtk.CheckButton()
+            alignment = gtk.Alignment(xscale = 0.0, yscale = 0.0,
+                                      xalign = 0.5, yalign = 0.5)
+            alignment.add(soundCheckButton)
+            table.attach(alignment, 2, 3, row, row+1)            
+            self._msgSoundCheckButtons[messageType] = soundCheckButton
+
+            mnemonicWidget = gtk.Label("")
+            table.attach(mnemonicWidget, 3, 4, row, row+1)
+            label.set_mnemonic_widget(mnemonicWidget)
+            mnemonicWidget.connect("mnemonic-activate",
+                                   self._msgLabelActivated,
+                                   messageType)
+
+            row += 1
+
+        return mainAlignment
+
+    def _msgLabelActivated(self, button, cycle_group, messageType):
+        """Called when the mnemonic of a label is activated.
+
+        It cycles the corresponding options."""
+        fsCheckButton = self._msgFSCheckButtons[messageType]
+        soundCheckButton = self._msgSoundCheckButtons[messageType]
+
+        num = 1 if fsCheckButton.get_active() else 0
+        num += 2 if soundCheckButton.get_active() else 0
+        num += 1
+
+        fsCheckButton.set_active((num&0x01)==0x01)
+        soundCheckButton.set_active((num&0x02)==0x02)
+
+        return True
+            
+    def _buildAdvanced(self):
+        """Build the page for the advanced settings."""
+
+        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                      xscale = 1.0, yscale = 0.0)
+        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
+                                  padding_left = 4, padding_right = 4)
+        mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        self._autoUpdate = gtk.CheckButton(xstr("prefs_update_auto"))
+        mainBox.pack_start(self._autoUpdate, False, False, 4)
+
+        self._autoUpdate.set_use_underline(True)
+        self._autoUpdate.connect("toggled", self._autoUpdateToggled)
+        self._autoUpdate.set_tooltip_text(xstr("prefs_update_auto_tooltip"))
+        self._warnedAutoUpdate = False
+        self._togglingAutoUpdate = False
+        
+        updateURLBox = gtk.HBox()
+        mainBox.pack_start(updateURLBox, False, False, 4)
+        label = gtk.Label(xstr("prefs_update_url"))
+        label.set_use_underline(True)
+        updateURLBox.pack_start(label, False, False, 4)
+
+        self._updateURL = gtk.Entry()
+        label.set_mnemonic_widget(self._updateURL)
+        self._updateURL.set_width_chars(40)
+        self._updateURL.set_tooltip_text(xstr("prefs_update_url_tooltip"))
+        self._updateURL.connect("changed", self._updateURLChanged)
+        updateURLBox.pack_start(self._updateURL, True, True, 4)
+
+        return mainAlignment
+
+    def _setOKButtonSensitivity(self):
+        """Set the sensitive state of the OK button."""
+        sensitive = False
+        try:
+            result = urlparse.urlparse(self._updateURL.get_text())
+            sensitive = result.scheme!="" and (result.netloc + result.path)!=""
+        except:
+            pass
+
+        okButton = self.get_widget_for_response(RESPONSETYPE_ACCEPT)
+        okButton.set_sensitive(sensitive)
+
+    def _autoUpdateToggled(self, button):
+        """Called when the auto update check button is toggled."""
+        if not self._togglingAutoUpdate and not self._warnedAutoUpdate and \
+           not self._autoUpdate.get_active():
+            dialog = gtk.MessageDialog(parent = self,
+                                       type = MESSAGETYPE_INFO,
+                                       message_format = xstr("prefs_update_auto_warning"))
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(self.get_title())
+            dialog.run()
+            dialog.hide()
+            self._warnedAutoUpdate = True
+            
+    def _updateURLChanged(self, entry):
+        """Called when the update URL is changed."""
+        self._setOKButtonSensitivity()
Index: src/mlx/gui/prefs.py
===================================================================
--- src/mlx/gui/prefs.py	(revision 151)
+++ 	(revision )
@@ -1,394 +1,0 @@
-# Module for the preferences dialog
-
-#------------------------------------------------------------------------------
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-
-import urlparse
-
-#------------------------------------------------------------------------------
-
-class Preferences(gtk.Dialog):
-    """The preferences dialog."""
-    def __init__(self, gui):
-        """Construct the dialog."""
-        super(Preferences, self).__init__(WINDOW_TITLE_BASE + " " +
-                                          xstr("prefs_title"),
-                                          gui.mainWindow,
-                                          DIALOG_MODAL)
-
-        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
-        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
-        
-        self._gui = gui
-
-        contentArea = self.get_content_area()
-
-        notebook = gtk.Notebook()
-        contentArea.pack_start(notebook, True, True, 4)
-
-        general = self._buildGeneral()
-        label = gtk.Label(xstr("prefs_tab_general"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("prefs_tab_general_tooltip"))
-        notebook.append_page(general, label)
-
-        messages = self._buildMessages()
-        label = gtk.Label(xstr("prefs_tab_messages"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("prefs_tab_message_tooltip"))
-        notebook.append_page(messages, label)
-
-        advanced = self._buildAdvanced()
-        label = gtk.Label(xstr("prefs_tab_advanced"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("prefs_tab_advanced_tooltip"))
-        notebook.append_page(advanced, label)
-
-    def run(self, config):
-        """Run the preferences dialog.
-
-        The dialog will be set up from data in the given configuration. If the
-        changes are accepted by the user, the configuration is updated and saved."""
-        self._fromConfig(config)
-
-        self.show_all()
-        response = super(Preferences, self).run()
-        self.hide()
-
-        if response==RESPONSETYPE_ACCEPT:
-            self._toConfig(config)
-            config.save()
-
-    def _fromConfig(self, config):
-        """Setup the dialog from the given configuration."""
-        self._setLanguage(config.language)
-        self._hideMinimizedWindow.set_active(config.hideMinimizedWindow)
-        self._onlineGateSystem.set_active(config.onlineGateSystem)
-        self._onlineACARS.set_active(config.onlineACARS)
-        self._flareTimeFromFS.set_active(config.flareTimeFromFS)
-        self._syncFSTime.set_active(config.syncFSTime)
-
-        pirepDirectory = config.pirepDirectory
-        self._pirepDirectory.set_text("" if pirepDirectory is None
-                                      else pirepDirectory)
-
-        for messageType in const.messageTypes:
-            level = config.getMessageTypeLevel(messageType)
-            button = self._msgFSCheckButtons[messageType]
-            button.set_active(level == const.MESSAGELEVEL_FS or
-                              level == const.MESSAGELEVEL_BOTH)
-            button = self._msgSoundCheckButtons[messageType]
-            button.set_active(level == const.MESSAGELEVEL_SOUND or
-                              level == const.MESSAGELEVEL_BOTH)
-
-        self._togglingAutoUpdate = True
-        self._autoUpdate.set_active(config.autoUpdate)
-        self._togglingAutoUpdate = False
-        if not config.autoUpdate:
-            self._warnedAutoUpdate = True
-
-        self._updateURL.set_text(config.updateURL)
-
-    def _toConfig(self, config):
-        """Setup the given config from the settings in the dialog."""
-        config.language = self._getLanguage()
-        config.hideMinimizedWindow = self._hideMinimizedWindow.get_active()
-        config.onlineGateSystem = self._onlineGateSystem.get_active()
-        config.onlineACARS = self._onlineACARS.get_active()
-        config.flareTimeFromFS = self._flareTimeFromFS.get_active()
-        config.syncFSTime = self._syncFSTime.get_active()
-        config.pirepDirectory = self._pirepDirectory.get_text()
-
-        for messageType in const.messageTypes:
-            fsButtonActive = self._msgFSCheckButtons[messageType].get_active()
-            soundButtonActive = self._msgSoundCheckButtons[messageType].get_active()
-            if fsButtonActive:
-                level = const.MESSAGELEVEL_BOTH if soundButtonActive \
-                        else const.MESSAGELEVEL_FS
-            elif soundButtonActive:
-                level = const.MESSAGELEVEL_SOUND
-            else:
-                level = const.MESSAGELEVEL_NONE
-            config.setMessageTypeLevel(messageType, level)
-
-        config.autoUpdate = self._autoUpdate.get_active()
-        config.updateURL = self._updateURL.get_text()
-
-    def _buildGeneral(self):
-        """Build the page for the general settings."""
-        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                      xscale = 1.0, yscale = 0.0)
-        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
-                                  padding_left = 4, padding_right = 4)
-        mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        languageBox = gtk.HBox()
-        mainBox.pack_start(languageBox, False, False, 4)
-
-        label = gtk.Label(xstr("prefs_language"))
-        label.set_use_underline(True)
-
-        languageBox.pack_start(label, False, False, 4)
-
-        self._languageList = gtk.ListStore(str, str)
-        for language in const.languages:
-            self._languageList.append([xstr("prefs_lang_" + language),
-                                       language])
-
-        self._languageComboBox = languageComboBox = \
-            gtk.ComboBox(model = self._languageList)                   
-        cell = gtk.CellRendererText()
-        languageComboBox.pack_start(cell, True)
-        languageComboBox.add_attribute(cell, 'text', 0)
-        languageComboBox.set_tooltip_text(xstr("prefs_language_tooltip"))
-        languageComboBox.connect("changed", self._languageChanged)
-        languageBox.pack_start(languageComboBox, False, False, 4)
-
-        label.set_mnemonic_widget(languageComboBox)
-
-        self._changingLanguage = False
-        self._warnedRestartNeeded = False
-
-        self._hideMinimizedWindow = gtk.CheckButton(xstr("prefs_hideMinimizedWindow"))
-        self._hideMinimizedWindow.set_use_underline(True)
-        self._hideMinimizedWindow.set_tooltip_text(xstr("prefs_hideMinimizedWindow_tooltip"))
-        mainBox.pack_start(self._hideMinimizedWindow, False, False, 4)
-
-        self._onlineGateSystem = gtk.CheckButton(xstr("prefs_onlineGateSystem"))
-        self._onlineGateSystem.set_use_underline(True)
-        self._onlineGateSystem.set_tooltip_text(xstr("prefs_onlineGateSystem_tooltip"))
-        mainBox.pack_start(self._onlineGateSystem, False, False, 4)
-
-        self._onlineACARS = gtk.CheckButton(xstr("prefs_onlineACARS"))
-        self._onlineACARS.set_use_underline(True)
-        self._onlineACARS.set_tooltip_text(xstr("prefs_onlineACARS_tooltip"))
-        mainBox.pack_start(self._onlineACARS, False, False, 4)
-
-        self._flareTimeFromFS = gtk.CheckButton(xstr("prefs_flaretimeFromFS"))
-        self._flareTimeFromFS.set_use_underline(True)
-        self._flareTimeFromFS.set_tooltip_text(xstr("prefs_flaretimeFromFS_tooltip"))
-        mainBox.pack_start(self._flareTimeFromFS, False, False, 4)
-                                       
-        self._syncFSTime = gtk.CheckButton(xstr("prefs_syncFSTime"))
-        self._syncFSTime.set_use_underline(True)
-        self._syncFSTime.set_tooltip_text(xstr("prefs_syncFSTime_tooltip"))
-        mainBox.pack_start(self._syncFSTime, False, False, 4)
-
-        pirepBox = gtk.HBox()
-        mainBox.pack_start(pirepBox, False, False, 4)
-
-        label = gtk.Label(xstr("prefs_pirepDirectory"))
-        label.set_use_underline(True)
-        pirepBox.pack_start(label, False, False, 4)
-
-        self._pirepDirectory = gtk.Entry()
-        self._pirepDirectory.set_tooltip_text(xstr("prefs_pirepDirectory_tooltip"))
-        label.set_mnemonic_widget(self._pirepDirectory)
-        pirepBox.pack_start(self._pirepDirectory, True, True, 4)
-
-        self._pirepDirectoryButton = gtk.Button(xstr("button_browse"))
-        self._pirepDirectoryButton.connect("clicked",
-                                           self._pirepDirectoryButtonClicked)
-        pirepBox.pack_start(self._pirepDirectoryButton, False, False, 4)
-        
-        return mainAlignment
-
-    def _setLanguage(self, language):
-        """Set the language to the given one."""
-        iter = self._languageList.get_iter_first()
-        while iter is not None:
-            (lang,) = self._languageList.get(iter, 1)
-            if (not language and lang=="$system") or \
-               lang==language:
-                self._changingLanguage = True
-                self._languageComboBox.set_active_iter(iter)
-                self._changingLanguage = False
-                break
-            else:
-                iter = self._languageList.iter_next(iter)
-
-    def _getLanguage(self):
-        """Get the language selected by the user."""        
-        iter = self._languageComboBox.get_active_iter()
-        (lang,) = self._languageList.get(iter, 1)
-        return "" if lang=="$system" else lang
-
-    def _languageChanged(self, comboBox):
-        """Called when the language has changed."""
-        if not self._changingLanguage and not self._warnedRestartNeeded:
-            dialog = gtk.MessageDialog(parent = self,
-                                       type = MESSAGETYPE_INFO,
-                                       message_format = xstr("prefs_restart"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(self.get_title())
-            dialog.format_secondary_markup(xstr("prefs_language_restart_sec"))
-            dialog.run()
-            dialog.hide()
-            self._warnedRestartNeeded = True
-       
-    def _pirepDirectoryButtonClicked(self, button):
-        """Called when the PIREP directory button is clicked."""
-        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                       xstr("prefs_pirepDirectory_browser_title"),
-                                       action = FILE_CHOOSER_ACTION_SELECT_FOLDER,
-                                       buttons = (gtk.STOCK_CANCEL, RESPONSETYPE_CANCEL,
-                                                  gtk.STOCK_OK, RESPONSETYPE_OK),
-                                       parent = self)
-        dialog.set_modal(True)
-        dialog.set_transient_for(self)
-
-        directory = self._pirepDirectory.get_text()
-        if directory:
-            dialog.select_filename(directory)
-        
-        result = dialog.run()
-        dialog.hide()
-
-        if result==RESPONSETYPE_OK:
-            self._pirepDirectory.set_text(dialog.get_filename())
-        
-    def _buildMessages(self):
-        """Build the page for the message settings."""
-
-        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                      xscale = 0.0, yscale = 0.0)
-        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
-                                  padding_left = 4, padding_right = 4)
-        mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        table = gtk.Table(len(const.messageTypes) + 1, 3)
-        table.set_row_spacings(8)
-        table.set_col_spacings(32)
-        table.set_homogeneous(False)
-        mainBox.pack_start(table, False, False, 4)
-        
-        label = gtk.Label(xstr("prefs_msgs_fs"))
-        label.set_justify(JUSTIFY_CENTER)
-        label.set_alignment(0.5, 1.0)
-        table.attach(label, 1, 2, 0, 1)
-        
-        label = gtk.Label(xstr("prefs_msgs_sound"))
-        label.set_justify(JUSTIFY_CENTER)
-        label.set_alignment(0.5, 1.0)
-        table.attach(label, 2, 3, 0, 1)
-
-        self._msgFSCheckButtons = {}
-        self._msgSoundCheckButtons = {}        
-        row = 1
-        for messageType in const.messageTypes:
-            messageTypeStr = const.messageType2string(messageType)
-            label = gtk.Label(xstr("prefs_msgs_type_" + messageTypeStr))
-            label.set_justify(JUSTIFY_CENTER)
-            label.set_use_underline(True)
-            label.set_alignment(0.5, 0.5)
-            table.attach(label, 0, 1, row, row+1)
-
-            fsCheckButton = gtk.CheckButton()
-            alignment = gtk.Alignment(xscale = 0.0, yscale = 0.0,
-                                      xalign = 0.5, yalign = 0.5)
-            alignment.add(fsCheckButton)
-            table.attach(alignment, 1, 2, row, row+1)
-            self._msgFSCheckButtons[messageType] = fsCheckButton
-            
-            soundCheckButton = gtk.CheckButton()
-            alignment = gtk.Alignment(xscale = 0.0, yscale = 0.0,
-                                      xalign = 0.5, yalign = 0.5)
-            alignment.add(soundCheckButton)
-            table.attach(alignment, 2, 3, row, row+1)            
-            self._msgSoundCheckButtons[messageType] = soundCheckButton
-
-            mnemonicWidget = gtk.Label("")
-            table.attach(mnemonicWidget, 3, 4, row, row+1)
-            label.set_mnemonic_widget(mnemonicWidget)
-            mnemonicWidget.connect("mnemonic-activate",
-                                   self._msgLabelActivated,
-                                   messageType)
-
-            row += 1
-
-        return mainAlignment
-
-    def _msgLabelActivated(self, button, cycle_group, messageType):
-        """Called when the mnemonic of a label is activated.
-
-        It cycles the corresponding options."""
-        fsCheckButton = self._msgFSCheckButtons[messageType]
-        soundCheckButton = self._msgSoundCheckButtons[messageType]
-
-        num = 1 if fsCheckButton.get_active() else 0
-        num += 2 if soundCheckButton.get_active() else 0
-        num += 1
-
-        fsCheckButton.set_active((num&0x01)==0x01)
-        soundCheckButton.set_active((num&0x02)==0x02)
-
-        return True
-            
-    def _buildAdvanced(self):
-        """Build the page for the advanced settings."""
-
-        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                      xscale = 1.0, yscale = 0.0)
-        mainAlignment.set_padding(padding_top = 16, padding_bottom = 8,
-                                  padding_left = 4, padding_right = 4)
-        mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        self._autoUpdate = gtk.CheckButton(xstr("prefs_update_auto"))
-        mainBox.pack_start(self._autoUpdate, False, False, 4)
-
-        self._autoUpdate.set_use_underline(True)
-        self._autoUpdate.connect("toggled", self._autoUpdateToggled)
-        self._autoUpdate.set_tooltip_text(xstr("prefs_update_auto_tooltip"))
-        self._warnedAutoUpdate = False
-        self._togglingAutoUpdate = False
-        
-        updateURLBox = gtk.HBox()
-        mainBox.pack_start(updateURLBox, False, False, 4)
-        label = gtk.Label(xstr("prefs_update_url"))
-        label.set_use_underline(True)
-        updateURLBox.pack_start(label, False, False, 4)
-
-        self._updateURL = gtk.Entry()
-        label.set_mnemonic_widget(self._updateURL)
-        self._updateURL.set_width_chars(40)
-        self._updateURL.set_tooltip_text(xstr("prefs_update_url_tooltip"))
-        self._updateURL.connect("changed", self._updateURLChanged)
-        updateURLBox.pack_start(self._updateURL, True, True, 4)
-
-        return mainAlignment
-
-    def _setOKButtonSensitivity(self):
-        """Set the sensitive state of the OK button."""
-        sensitive = False
-        try:
-            result = urlparse.urlparse(self._updateURL.get_text())
-            sensitive = result.scheme!="" and (result.netloc + result.path)!=""
-        except:
-            pass
-
-        okButton = self.get_widget_for_response(RESPONSETYPE_ACCEPT)
-        okButton.set_sensitive(sensitive)
-
-    def _autoUpdateToggled(self, button):
-        """Called when the auto update check button is toggled."""
-        if not self._togglingAutoUpdate and not self._warnedAutoUpdate and \
-           not self._autoUpdate.get_active():
-            dialog = gtk.MessageDialog(parent = self,
-                                       type = MESSAGETYPE_INFO,
-                                       message_format = xstr("prefs_update_auto_warning"))
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(self.get_title())
-            dialog.run()
-            dialog.hide()
-            self._warnedAutoUpdate = True
-            
-    def _updateURLChanged(self, entry):
-        """Called when the update URL is changed."""
-        self._setOKButtonSensitivity()
Index: src/mlx/gui/statusbar.py
===================================================================
--- src/mlx/gui/statusbar.py	(revision 116)
+++ src/mlx/gui/statusbar.py	(revision 116)
@@ -0,0 +1,138 @@
+# Implementation of the status bar.
+
+#-------------------------------------------------------------------------------
+
+from common import *
+
+import mlx.const as const
+from mlx.i18n import xstr
+
+import math
+import time
+
+#-------------------------------------------------------------------------------
+
+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(xstr("statusbar_conn_tooltip"))
+
+        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()
+        longestStage = xstr("flight_stage_" +
+                            const.stage2string(const.STAGE_PUSHANDTAXI))
+        self._stageLabel.set_width_chars(len(longestStage) + 3)
+        self._stageLabel.set_tooltip_text(xstr("statusbar_stage_tooltip"))
+        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._timeLabel = gtk.Label("--:--:--")
+        self._timeLabel.set_width_chars(8)
+        self._timeLabel.set_tooltip_text(xstr("statusbar_time_tooltip"))
+        self._timeLabel.set_alignment(1.0, 0.5)
+        
+        statusBox.pack_start(self._timeLabel, 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(xstr("statusbar_rating_tooltip"))
+        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(xstr("statusbar_busy_tooltip"))
+        self._busyLabel.set_alignment(1.0, 0.5)
+        statusBox.pack_start(self._busyLabel, True, True, 8)
+        
+        self._updateFlightStatus()
+        self.updateTime()
+
+    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 updateTime(self, t = None):
+        """Update the time"""
+        timeStr = "--:--:--" if t is None \
+                  else time.strftime("%H:%M:%S", time.gmtime(t))
+        
+        self._timeLabel.set_text(timeStr)
+
+    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."""
+        if self._stage is None:
+            text = "-"
+        else:
+            text = xstr("flight_stage_" + const.stage2string(self._stage)).upper()
+        self._stageLabel.set_text(text)
+    
+        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 116)
+++ 	(revision )
@@ -1,138 +1,0 @@
-# Implementation of the status bar.
-
-#-------------------------------------------------------------------------------
-
-from common import *
-
-import mlx.const as const
-from mlx.i18n import xstr
-
-import math
-import time
-
-#-------------------------------------------------------------------------------
-
-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(xstr("statusbar_conn_tooltip"))
-
-        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()
-        longestStage = xstr("flight_stage_" +
-                            const.stage2string(const.STAGE_PUSHANDTAXI))
-        self._stageLabel.set_width_chars(len(longestStage) + 3)
-        self._stageLabel.set_tooltip_text(xstr("statusbar_stage_tooltip"))
-        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._timeLabel = gtk.Label("--:--:--")
-        self._timeLabel.set_width_chars(8)
-        self._timeLabel.set_tooltip_text(xstr("statusbar_time_tooltip"))
-        self._timeLabel.set_alignment(1.0, 0.5)
-        
-        statusBox.pack_start(self._timeLabel, 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(xstr("statusbar_rating_tooltip"))
-        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(xstr("statusbar_busy_tooltip"))
-        self._busyLabel.set_alignment(1.0, 0.5)
-        statusBox.pack_start(self._busyLabel, True, True, 8)
-        
-        self._updateFlightStatus()
-        self.updateTime()
-
-    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 updateTime(self, t = None):
-        """Update the time"""
-        timeStr = "--:--:--" if t is None \
-                  else time.strftime("%H:%M:%S", time.gmtime(t))
-        
-        self._timeLabel.set_text(timeStr)
-
-    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."""
-        if self._stage is None:
-            text = "-"
-        else:
-            text = xstr("flight_stage_" + const.stage2string(self._stage)).upper()
-        self._stageLabel.set_text(text)
-    
-        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 128)
+++ src/mlx/gui/statusicon.py	(revision 128)
@@ -0,0 +1,166 @@
+# Implementation of the status icon
+
+#-------------------------------------------------------------------------------
+
+from common import *
+
+import mlx.const as const
+from mlx.i18n import xstr
+
+#-------------------------------------------------------------------------------
+
+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(xstr("statusicon_showmain"))  
+        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(xstr("statusicon_showmonitor"))  
+        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(xstr("statusicon_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 = u"-" if self._stage is None \
+                else xstr("flight_stage_" + const.stage2string(self._stage))
+        
+        if self._noGoReason is None:
+            rating = "%.0f%%" % (self._rating,)
+        else:
+            rating = self._noGoReason
+
+        if appIndicator:
+            self._stageMenuItem.set_label("%s: %s" % \
+                                          (xstr("statusicon_stage"),
+                                           stage))
+            self._ratingMenuItem.set_label("%s: %s" % \
+                                           (xstr("statusicon_rating"),
+                                            rating))
+        else:
+            if self._noGoReason is not None:
+                rating = '<span foreground="red">' + rating + '</span>'
+            markup = u"MAVA Logger X %s\n\n%s: %s\n%s: %s" %\
+                     (const.VERSION, xstr("statusicon_stage"), stage,
+                      xstr("statusicon_rating"), rating)
+            self._statusIcon.set_tooltip_markup(markup)
Index: src/mlx/gui/statusicon.py
===================================================================
--- src/mlx/gui/statusicon.py	(revision 128)
+++ 	(revision )
@@ -1,166 +1,0 @@
-# Implementation of the status icon
-
-#-------------------------------------------------------------------------------
-
-from common import *
-
-import mlx.const as const
-from mlx.i18n import xstr
-
-#-------------------------------------------------------------------------------
-
-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(xstr("statusicon_showmain"))  
-        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(xstr("statusicon_showmonitor"))  
-        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(xstr("statusicon_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 = u"-" if self._stage is None \
-                else xstr("flight_stage_" + const.stage2string(self._stage))
-        
-        if self._noGoReason is None:
-            rating = "%.0f%%" % (self._rating,)
-        else:
-            rating = self._noGoReason
-
-        if appIndicator:
-            self._stageMenuItem.set_label("%s: %s" % \
-                                          (xstr("statusicon_stage"),
-                                           stage))
-            self._ratingMenuItem.set_label("%s: %s" % \
-                                           (xstr("statusicon_rating"),
-                                            rating))
-        else:
-            if self._noGoReason is not None:
-                rating = '<span foreground="red">' + rating + '</span>'
-            markup = u"MAVA Logger X %s\n\n%s: %s\n%s: %s" %\
-                     (const.VERSION, xstr("statusicon_stage"), stage,
-                      xstr("statusicon_rating"), rating)
-            self._statusIcon.set_tooltip_markup(markup)
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 124)
+++ src/mlx/gui/update.py	(revision 124)
@@ -0,0 +1,282 @@
+# The GUI part of the update
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.update import update
+from mlx.i18n import xstr
+
+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(WINDOW_TITLE_BASE + " " + xstr("update_title"))
+        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(WINDOW_TITLE_BASE + " " + xstr("update_title"),
+                       parentWindow,
+                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL)
+        sudoDialog.add_button(xstr("button_cancel"), 0)
+        sudoDialog.add_button(xstr("button_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(xstr("update_needsudo"))
+        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(xstr("update_manifest_progress"))
+        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(xstr("update_manifest_done"))
+        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(xstr("update_files_progress"))
+
+    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(xstr("update_files_bytes") % \
+                                     (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(xstr("update_renaming"))
+
+    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(xstr("update_renamed") % (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(xstr("update_removing"))
+
+    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(xstr("update_removed") % (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(xstr("update_writing_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(xstr("update_finished"))
+            self._progressOKButton.set_sensitive(True)
+        else:
+            self._progressLabel.set_text(xstr("update_nothing"))
+        
+    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(xstr("update_failed"))
+        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 124)
+++ 	(revision )
@@ -1,282 +1,0 @@
-# The GUI part of the update
-
-#-------------------------------------------------------------------------------
-
-from mlx.gui.common import *
-
-from mlx.update import update
-from mlx.i18n import xstr
-
-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(WINDOW_TITLE_BASE + " " + xstr("update_title"))
-        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(WINDOW_TITLE_BASE + " " + xstr("update_title"),
-                       parentWindow,
-                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL)
-        sudoDialog.add_button(xstr("button_cancel"), 0)
-        sudoDialog.add_button(xstr("button_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(xstr("update_needsudo"))
-        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(xstr("update_manifest_progress"))
-        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(xstr("update_manifest_done"))
-        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(xstr("update_files_progress"))
-
-    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(xstr("update_files_bytes") % \
-                                     (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(xstr("update_renaming"))
-
-    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(xstr("update_renamed") % (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(xstr("update_removing"))
-
-    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(xstr("update_removed") % (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(xstr("update_writing_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(xstr("update_finished"))
-            self._progressOKButton.set_sensitive(True)
-        else:
-            self._progressLabel.set_text(xstr("update_nothing"))
-        
-    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(xstr("update_failed"))
-        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/weighthelp.py
===================================================================
--- src/mlx/gui/weighthelp.py	(revision 117)
+++ src/mlx/gui/weighthelp.py	(revision 117)
@@ -0,0 +1,487 @@
+# The weight calculation help window
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.i18n import xstr
+from mlx.checks import PayloadChecker
+
+#-------------------------------------------------------------------------------
+
+class WeightHelp(gtk.VBox):
+    """The weight calculation help tab."""
+    @staticmethod
+    def _getMarkup(value, expectedValue = None, tolerance = None):
+        """Get the markup for the given value.
+
+        If it is too much different from the expected value, it will be
+        colored yellow (if within the tolerance), or red (if out of the tolerance)."""
+        markup = "%.0f" % (value,)
+        if expectedValue is not None and tolerance is not None:
+            colour = None
+            diff = abs(value - expectedValue)
+            if diff>tolerance: colour = "red"
+            elif (diff*10)>=tolerance: colour = "orange"
+            else: colour = "darkgreen"
+            if colour is not None:
+                markup = '<span foreground="' + colour + '">' + markup + '</span>'
+        return markup
+
+    def __init__(self, gui):
+        """Construct the tab."""
+        super(WeightHelp, self).__init__()
+
+        self._gui = gui
+
+        mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                      xscale = 1.0, yscale = 1.0)
+        mainAlignment.set_padding(padding_top = 4, padding_bottom = 4,
+                                  padding_left = 12, padding_right = 12)
+        self.add(mainAlignment)
+
+        self._mainBox = mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        self._usingHelp = gtk.CheckButton(xstr("weighthelp_usinghelp"))
+        self._usingHelp.set_use_underline(True)
+        self._usingHelp.set_tooltip_text(xstr("weighthelp_usinghelp_tooltip"))
+        self._usingHelp.connect("toggled", self._usingHelpToggled)
+        mainBox.pack_start(self._usingHelp, False, False, 4)
+
+        
+        self._weightsTable = table = gtk.Table(16, 5)
+        table.set_homogeneous(False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(table)
+        mainBox.pack_start(alignment, True, True, 4)
+
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.set_padding(padding_bottom = 16, padding_top = 0,
+                              padding_left = 0, padding_right = 0)
+        label = gtk.Label(xstr("weighthelp_header_calculated"))
+        label.set_use_markup(True)
+        # FIXME: should be a constant in common
+        label.set_justify(gtk.Justification.CENTER if pygobject
+                          else gtk.JUSTIFY_CENTER)        
+        alignment.add(label)
+        table.attach(alignment, 1, 2, 0, 1)
+        
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.set_padding(padding_bottom = 16, padding_top = 0,
+                              padding_left = 0, padding_right = 0)
+        button = gtk.Button(xstr("weighthelp_header_simulator"))
+        button.set_tooltip_markup(xstr("weighthelp_header_simulator_tooltip"))
+        button.connect("clicked", self._fsButtonClicked)
+        label = button.get_child()
+        label.set_justify(gtk.Justification.CENTER if pygobject
+                          else gtk.JUSTIFY_CENTER)        
+        alignment.add(button)
+        table.attach(alignment, 3, 4, 0, 1)
+        
+
+        self._crewLabel = gtk.Label(xstr("weighthelp_crew") % ("99",))
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._crewLabel)
+        table.attach(alignment, 0, 1, 1, 2)
+
+        self._crewWeight = gtk.Label("0")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._crewWeight)
+        table.attach(alignment, 1, 2, 1, 2)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 1, 2)
+
+        text = xstr("weighthelp_pax") % ("999",)
+        self._paxLabel = gtk.Label(text)
+        self._paxLabel.set_width_chars(len(text))
+        self._paxLabel.set_alignment(0.0, 0.5)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._paxLabel)
+        table.attach(alignment, 0, 1, 2, 3)
+
+        self._paxWeight = gtk.Label("20000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._paxWeight)
+        table.attach(alignment, 1, 2, 2, 3)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 2, 3)
+        
+        label = gtk.Label(xstr("weighthelp_baggage"))
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 3, 4)
+
+        self._bagWeight = gtk.Label("2000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._bagWeight)
+        table.attach(alignment, 1, 2, 3, 4)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
+        
+        label = gtk.Label(xstr("weighthelp_cargo"))
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 4, 5)
+
+        self._cargoWeight = gtk.Label("2000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._cargoWeight)
+        table.attach(alignment, 1, 2, 4, 5)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 4, 5)
+        
+        label = gtk.Label(xstr("weighthelp_mail"))
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 5, 6)
+
+        self._mailWeight = gtk.Label("2000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._mailWeight)
+        table.attach(alignment, 1, 2, 5, 6)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 5, 6)
+
+        table.attach(gtk.HSeparator(), 1, 2, 6, 7)
+
+        label = gtk.Label("<b>" + xstr("weighthelp_payload") + "</b>")
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 7, 8)
+
+        self._payload = gtk.Label("<b>32000</b>")
+        self._payload.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._payload)
+        table.attach(alignment, 1, 2, 7, 8)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 7, 8)
+
+        self._fsPayload = gtk.Label("<b>32001</b>")
+        self._fsPayload.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._fsPayload)
+        table.attach(alignment, 3, 4, 7, 8)
+        
+        table.attach(gtk.Label("kg"), 4, 5, 7, 8)
+
+        label = gtk.Label(xstr("weighthelp_dow"))
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 8, 9)
+
+        self._dow = gtk.Label("35000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._dow)
+        table.attach(alignment, 1, 2, 8, 9)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 8, 9)
+
+        self._fsDOW = gtk.Label("33012")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._fsDOW)
+        table.attach(alignment, 3, 4, 8, 9)
+        
+        table.attach(gtk.Label("kg"), 4, 5, 8, 9)
+
+        table.attach(gtk.HSeparator(), 1, 2, 9, 10)
+
+        table.attach(gtk.HSeparator(), 3, 4, 9, 10)
+
+        label = gtk.Label("<b>" + xstr("weighthelp_zfw") + "</b>")
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 10, 11)
+
+        self._zfw = gtk.Label("<b>122000</b>")
+        self._zfw.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._zfw)
+        table.attach(alignment, 1, 2, 10, 11)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 10, 11)
+
+        self._fsZFW = gtk.Label("<b>124000</b>")
+        self._fsZFW.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._fsZFW)
+        table.attach(alignment, 3, 4, 10, 11)
+        
+        table.attach(gtk.Label("kg"), 4, 5, 10, 11)
+
+        table.attach(gtk.HSeparator(), 0, 5, 11, 12)
+        
+        label = gtk.Label(xstr("weighthelp_gross"))
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 12, 13)
+
+        self._fsGross = gtk.Label("124000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._fsGross)
+        table.attach(alignment, 3, 4, 12, 13)
+        
+        table.attach(gtk.Label("kg"), 4, 5, 12, 13)
+
+        label = gtk.Label(xstr("weighthelp_mzfw"))
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 13, 14)
+
+        self._mzfw = gtk.Label("35000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._mzfw)
+        table.attach(alignment, 1, 2, 13, 14)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 13, 14)
+
+        label = gtk.Label(xstr("weighthelp_mtow"))
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 14, 15)
+
+        self._mtow = gtk.Label("35000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._mtow)
+        table.attach(alignment, 1, 2, 14, 15)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 14, 15)
+
+        label = gtk.Label(xstr("weighthelp_mlw"))
+        label.set_use_markup(True)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(label)
+        table.attach(alignment, 0, 1, 15, 16)
+
+        self._mlw = gtk.Label("35000")
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(self._mlw)
+        table.attach(alignment, 1, 2, 15, 16)
+        
+        table.attach(gtk.Label("kg"), 2, 3, 15, 16)
+
+        self.show_all()
+
+    def disable(self):
+        """Disable the widget."""
+        self._mainBox.set_sensitive(False)
+    
+    def enable(self):
+        """Enable the widget."""
+        self._mainBox.set_sensitive(True)
+    
+    def reset(self):
+        """Reset all calculated and FS data."""
+        
+        self._usingHelp.set_active(False)
+        self._usingHelp.set_sensitive(True)
+        self._weightsTable.set_sensitive(False)
+        
+        self._crew = -1
+        self._pax = -1
+        self._humanWeight = 82.0
+        self._bag = -1
+        self._cargo = -1
+        self._mail = -1        
+        self._dowValue = -1
+        self._mzfwValue = -1
+        self._mtowValue = -1
+        self._mlwValue = -1
+
+        self._fsPayloadValue = -1
+        self._fsDOWValue = -1
+        self._fsZFWValue = -1
+        self._fsGrossValue = -1
+        
+        self._setupCalculated()
+        self._setupFS()
+
+    def _setupCalculated(self):
+        """Setup the labels for the calculated values."""
+        if self._crew<0:
+            self._crewLabel.set_text(xstr("weighthelp_crew") % ("-",))
+            self._crewWeight.set_text("-")
+        else:
+            self._crewLabel.set_text(xstr("weighthelp_crew") % (str(self._crew),))
+            crewWeight = self._crew * self._humanWeight
+            self._crewWeight.set_text("%.0f" % (crewWeight,))
+            
+        if self._pax<0:
+            self._paxLabel.set_text(xstr("weighthelp_pax") % ("-",))
+            self._paxWeight.set_text("-")
+        else:
+            self._paxLabel.set_text(xstr("weighthelp_pax") % (str(self._pax),))
+            paxWeight = self._pax * self._humanWeight
+            self._paxWeight.set_text("%.0f" % (paxWeight,))
+
+        self._setWeightLabel(self._bagWeight, self._bag)
+
+        self._setWeightLabel(self._cargoWeight, self._cargo)
+        
+        self._setWeightLabel(self._mailWeight, self._mail)
+            
+        (payload, zfw) = self._calculateWeights()
+
+        self._setWeightLabel(self._payload, payload, bold = True)
+            
+        self._setWeightLabel(self._dow, self._dowValue)
+
+        if zfw<0:
+            self._zfw.set_text("-")
+        else:
+            markup = "%.0f" % (zfw,)
+            if self._mzfwValue>0 and zfw>self._mzfwValue:
+                markup = '<span foreground="red">' + markup + '</span>'
+            markup = '<b>' + markup + '</b>'
+            self._zfw.set_markup(markup)
+
+        self._setWeightLabel(self._mzfw, self._mzfwValue)
+        
+        self._setWeightLabel(self._mtow, self._mtowValue)
+        
+        self._setWeightLabel(self._mlw, self._mlwValue)
+
+    def _setupFS(self):
+        if self._dowValue<0:
+            self._dow.set_text("-")
+        else:
+            self._dow.set_text("%.0f" % (self._dowValue,))
+
+        """Setup the labels for the FS values."""        
+        (payload, zfw) = self._calculateWeights()
+
+        if self._fsPayloadValue<0:
+            self._fsPayload.set_text("-")
+        else:
+            markup = WeightHelp._getMarkup(self._fsPayloadValue, payload,
+                                           PayloadChecker.TOLERANCE)
+            self._fsPayload.set_markup("<b>" + markup + "</b>")
+
+        if self._fsDOWValue<0:
+            self._fsDOW.set_text("-")
+        else:            
+            markup = WeightHelp._getMarkup(self._fsDOWValue,  self._dowValue,
+                                           PayloadChecker.TOLERANCE)
+            self._fsDOW.set_markup(markup)
+
+        if self._fsZFWValue<0:
+            self._fsZFW.set_text("-")
+        else:
+            markup = WeightHelp._getMarkup(self._fsZFWValue,  zfw,
+                                           PayloadChecker.TOLERANCE)
+            self._fsZFW.set_markup("<b>" + markup + "</b>")
+
+        self._setWeightLabel(self._fsGross, self._fsGrossValue)
+
+    def _calculateWeights(self):
+        """Calculate the payload and the zero-fuel weight.
+
+        It returns a tuple with these two items. If any of the items cannot be
+        calculated, that is -1."""
+        if self._crew<0 or self._pax<0 or \
+               self._bag<0 or self._cargo<0 or self._mail<0:
+            payload = -1
+        else:
+            payload = (self._crew + self._pax) * self._humanWeight + \
+                      self._bag + self._cargo + self._mail
+
+        if payload<0 or self._dowValue<0:
+            zfw = -1
+        else:
+            zfw = payload + self._dowValue
+
+        return (payload, zfw)
+
+    def _usingHelpToggled(self, button):
+        """Called when the Using help button is toggled."""
+        assert self._usingHelp.get_active()
+        self._usingHelp.set_sensitive(False)
+
+        self._gui.logger.untimedMessage("The weight calculation help function was used by the pilot")
+
+        bookedFlight = self._gui.bookedFlight
+        self._crew = bookedFlight.numCrew
+        self._pax = bookedFlight.numPassengers
+        self._bag = bookedFlight.bagWeight
+        self._cargo = self._gui.cargoWeight
+        self._mail = bookedFlight.mailWeight
+        
+        aircraft = self._gui.flight.aircraft
+        self._humanWeight = aircraft.humanWeight
+        self._dowValue = aircraft.dow
+        self._mzfwValue = aircraft.mzfw
+        self._mtowValue = aircraft.mtow
+        self._mlwValue = aircraft.mlw
+
+        self._setupCalculated()
+        self._weightsTable.set_sensitive(True)
+
+    def _fsButtonClicked(self, button):
+        """Callback for the FS button being clicked."""
+        gui = self._gui
+        gui.beginBusy(xstr("weighthelp_busy"))
+        gui.simulator.requestWeights(self._handleWeights)
+
+    def _handleWeights(self, dow, payload, zfw, grossWeight):
+        """Handle the given weights."""
+        gobject.idle_add(self._processWeights, dow, payload, zfw, grossWeight)
+
+    def _processWeights(self, dow, payload, zfw, grossWeight):
+        """Process the given weights."""
+        self._gui.endBusy()
+        if self._usingHelp.get_active():
+            self._fsPayloadValue = payload
+            self._fsDOWValue = dow
+            self._fsZFWValue = zfw
+            self._fsGrossValue = grossWeight
+            self._setupFS()
+
+    def _setWeightLabel(self, label, weight, bold = False):
+        """Set the given weight label to the given weight."""
+        if weight<0:
+            label.set_text("-")
+        else:
+            markup = "%.0f" % (weight,)
+            if bold: markup = "<b>" + markup + "</b>"
+            label.set_markup(markup)
+                
+        
Index: src/mlx/gui/weighthelp.py
===================================================================
--- src/mlx/gui/weighthelp.py	(revision 117)
+++ 	(revision )
@@ -1,487 +1,0 @@
-# The weight calculation help window
-
-#-------------------------------------------------------------------------------
-
-from mlx.gui.common import *
-
-from mlx.i18n import xstr
-from mlx.checks import PayloadChecker
-
-#-------------------------------------------------------------------------------
-
-class WeightHelp(gtk.VBox):
-    """The weight calculation help tab."""
-    @staticmethod
-    def _getMarkup(value, expectedValue = None, tolerance = None):
-        """Get the markup for the given value.
-
-        If it is too much different from the expected value, it will be
-        colored yellow (if within the tolerance), or red (if out of the tolerance)."""
-        markup = "%.0f" % (value,)
-        if expectedValue is not None and tolerance is not None:
-            colour = None
-            diff = abs(value - expectedValue)
-            if diff>tolerance: colour = "red"
-            elif (diff*10)>=tolerance: colour = "orange"
-            else: colour = "darkgreen"
-            if colour is not None:
-                markup = '<span foreground="' + colour + '">' + markup + '</span>'
-        return markup
-
-    def __init__(self, gui):
-        """Construct the tab."""
-        super(WeightHelp, self).__init__()
-
-        self._gui = gui
-
-        mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                      xscale = 1.0, yscale = 1.0)
-        mainAlignment.set_padding(padding_top = 4, padding_bottom = 4,
-                                  padding_left = 12, padding_right = 12)
-        self.add(mainAlignment)
-
-        self._mainBox = mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        self._usingHelp = gtk.CheckButton(xstr("weighthelp_usinghelp"))
-        self._usingHelp.set_use_underline(True)
-        self._usingHelp.set_tooltip_text(xstr("weighthelp_usinghelp_tooltip"))
-        self._usingHelp.connect("toggled", self._usingHelpToggled)
-        mainBox.pack_start(self._usingHelp, False, False, 4)
-
-        
-        self._weightsTable = table = gtk.Table(16, 5)
-        table.set_homogeneous(False)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(table)
-        mainBox.pack_start(alignment, True, True, 4)
-
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.0,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.set_padding(padding_bottom = 16, padding_top = 0,
-                              padding_left = 0, padding_right = 0)
-        label = gtk.Label(xstr("weighthelp_header_calculated"))
-        label.set_use_markup(True)
-        # FIXME: should be a constant in common
-        label.set_justify(gtk.Justification.CENTER if pygobject
-                          else gtk.JUSTIFY_CENTER)        
-        alignment.add(label)
-        table.attach(alignment, 1, 2, 0, 1)
-        
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.0,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.set_padding(padding_bottom = 16, padding_top = 0,
-                              padding_left = 0, padding_right = 0)
-        button = gtk.Button(xstr("weighthelp_header_simulator"))
-        button.set_tooltip_markup(xstr("weighthelp_header_simulator_tooltip"))
-        button.connect("clicked", self._fsButtonClicked)
-        label = button.get_child()
-        label.set_justify(gtk.Justification.CENTER if pygobject
-                          else gtk.JUSTIFY_CENTER)        
-        alignment.add(button)
-        table.attach(alignment, 3, 4, 0, 1)
-        
-
-        self._crewLabel = gtk.Label(xstr("weighthelp_crew") % ("99",))
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._crewLabel)
-        table.attach(alignment, 0, 1, 1, 2)
-
-        self._crewWeight = gtk.Label("0")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._crewWeight)
-        table.attach(alignment, 1, 2, 1, 2)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 1, 2)
-
-        text = xstr("weighthelp_pax") % ("999",)
-        self._paxLabel = gtk.Label(text)
-        self._paxLabel.set_width_chars(len(text))
-        self._paxLabel.set_alignment(0.0, 0.5)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._paxLabel)
-        table.attach(alignment, 0, 1, 2, 3)
-
-        self._paxWeight = gtk.Label("20000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._paxWeight)
-        table.attach(alignment, 1, 2, 2, 3)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 2, 3)
-        
-        label = gtk.Label(xstr("weighthelp_baggage"))
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 3, 4)
-
-        self._bagWeight = gtk.Label("2000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._bagWeight)
-        table.attach(alignment, 1, 2, 3, 4)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 3, 4)
-        
-        label = gtk.Label(xstr("weighthelp_cargo"))
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 4, 5)
-
-        self._cargoWeight = gtk.Label("2000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._cargoWeight)
-        table.attach(alignment, 1, 2, 4, 5)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 4, 5)
-        
-        label = gtk.Label(xstr("weighthelp_mail"))
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 5, 6)
-
-        self._mailWeight = gtk.Label("2000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._mailWeight)
-        table.attach(alignment, 1, 2, 5, 6)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 5, 6)
-
-        table.attach(gtk.HSeparator(), 1, 2, 6, 7)
-
-        label = gtk.Label("<b>" + xstr("weighthelp_payload") + "</b>")
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 7, 8)
-
-        self._payload = gtk.Label("<b>32000</b>")
-        self._payload.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._payload)
-        table.attach(alignment, 1, 2, 7, 8)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 7, 8)
-
-        self._fsPayload = gtk.Label("<b>32001</b>")
-        self._fsPayload.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._fsPayload)
-        table.attach(alignment, 3, 4, 7, 8)
-        
-        table.attach(gtk.Label("kg"), 4, 5, 7, 8)
-
-        label = gtk.Label(xstr("weighthelp_dow"))
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 8, 9)
-
-        self._dow = gtk.Label("35000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._dow)
-        table.attach(alignment, 1, 2, 8, 9)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 8, 9)
-
-        self._fsDOW = gtk.Label("33012")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._fsDOW)
-        table.attach(alignment, 3, 4, 8, 9)
-        
-        table.attach(gtk.Label("kg"), 4, 5, 8, 9)
-
-        table.attach(gtk.HSeparator(), 1, 2, 9, 10)
-
-        table.attach(gtk.HSeparator(), 3, 4, 9, 10)
-
-        label = gtk.Label("<b>" + xstr("weighthelp_zfw") + "</b>")
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 10, 11)
-
-        self._zfw = gtk.Label("<b>122000</b>")
-        self._zfw.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._zfw)
-        table.attach(alignment, 1, 2, 10, 11)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 10, 11)
-
-        self._fsZFW = gtk.Label("<b>124000</b>")
-        self._fsZFW.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._fsZFW)
-        table.attach(alignment, 3, 4, 10, 11)
-        
-        table.attach(gtk.Label("kg"), 4, 5, 10, 11)
-
-        table.attach(gtk.HSeparator(), 0, 5, 11, 12)
-        
-        label = gtk.Label(xstr("weighthelp_gross"))
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 12, 13)
-
-        self._fsGross = gtk.Label("124000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._fsGross)
-        table.attach(alignment, 3, 4, 12, 13)
-        
-        table.attach(gtk.Label("kg"), 4, 5, 12, 13)
-
-        label = gtk.Label(xstr("weighthelp_mzfw"))
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 13, 14)
-
-        self._mzfw = gtk.Label("35000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._mzfw)
-        table.attach(alignment, 1, 2, 13, 14)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 13, 14)
-
-        label = gtk.Label(xstr("weighthelp_mtow"))
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 14, 15)
-
-        self._mtow = gtk.Label("35000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._mtow)
-        table.attach(alignment, 1, 2, 14, 15)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 14, 15)
-
-        label = gtk.Label(xstr("weighthelp_mlw"))
-        label.set_use_markup(True)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(label)
-        table.attach(alignment, 0, 1, 15, 16)
-
-        self._mlw = gtk.Label("35000")
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(self._mlw)
-        table.attach(alignment, 1, 2, 15, 16)
-        
-        table.attach(gtk.Label("kg"), 2, 3, 15, 16)
-
-        self.show_all()
-
-    def disable(self):
-        """Disable the widget."""
-        self._mainBox.set_sensitive(False)
-    
-    def enable(self):
-        """Enable the widget."""
-        self._mainBox.set_sensitive(True)
-    
-    def reset(self):
-        """Reset all calculated and FS data."""
-        
-        self._usingHelp.set_active(False)
-        self._usingHelp.set_sensitive(True)
-        self._weightsTable.set_sensitive(False)
-        
-        self._crew = -1
-        self._pax = -1
-        self._humanWeight = 82.0
-        self._bag = -1
-        self._cargo = -1
-        self._mail = -1        
-        self._dowValue = -1
-        self._mzfwValue = -1
-        self._mtowValue = -1
-        self._mlwValue = -1
-
-        self._fsPayloadValue = -1
-        self._fsDOWValue = -1
-        self._fsZFWValue = -1
-        self._fsGrossValue = -1
-        
-        self._setupCalculated()
-        self._setupFS()
-
-    def _setupCalculated(self):
-        """Setup the labels for the calculated values."""
-        if self._crew<0:
-            self._crewLabel.set_text(xstr("weighthelp_crew") % ("-",))
-            self._crewWeight.set_text("-")
-        else:
-            self._crewLabel.set_text(xstr("weighthelp_crew") % (str(self._crew),))
-            crewWeight = self._crew * self._humanWeight
-            self._crewWeight.set_text("%.0f" % (crewWeight,))
-            
-        if self._pax<0:
-            self._paxLabel.set_text(xstr("weighthelp_pax") % ("-",))
-            self._paxWeight.set_text("-")
-        else:
-            self._paxLabel.set_text(xstr("weighthelp_pax") % (str(self._pax),))
-            paxWeight = self._pax * self._humanWeight
-            self._paxWeight.set_text("%.0f" % (paxWeight,))
-
-        self._setWeightLabel(self._bagWeight, self._bag)
-
-        self._setWeightLabel(self._cargoWeight, self._cargo)
-        
-        self._setWeightLabel(self._mailWeight, self._mail)
-            
-        (payload, zfw) = self._calculateWeights()
-
-        self._setWeightLabel(self._payload, payload, bold = True)
-            
-        self._setWeightLabel(self._dow, self._dowValue)
-
-        if zfw<0:
-            self._zfw.set_text("-")
-        else:
-            markup = "%.0f" % (zfw,)
-            if self._mzfwValue>0 and zfw>self._mzfwValue:
-                markup = '<span foreground="red">' + markup + '</span>'
-            markup = '<b>' + markup + '</b>'
-            self._zfw.set_markup(markup)
-
-        self._setWeightLabel(self._mzfw, self._mzfwValue)
-        
-        self._setWeightLabel(self._mtow, self._mtowValue)
-        
-        self._setWeightLabel(self._mlw, self._mlwValue)
-
-    def _setupFS(self):
-        if self._dowValue<0:
-            self._dow.set_text("-")
-        else:
-            self._dow.set_text("%.0f" % (self._dowValue,))
-
-        """Setup the labels for the FS values."""        
-        (payload, zfw) = self._calculateWeights()
-
-        if self._fsPayloadValue<0:
-            self._fsPayload.set_text("-")
-        else:
-            markup = WeightHelp._getMarkup(self._fsPayloadValue, payload,
-                                           PayloadChecker.TOLERANCE)
-            self._fsPayload.set_markup("<b>" + markup + "</b>")
-
-        if self._fsDOWValue<0:
-            self._fsDOW.set_text("-")
-        else:            
-            markup = WeightHelp._getMarkup(self._fsDOWValue,  self._dowValue,
-                                           PayloadChecker.TOLERANCE)
-            self._fsDOW.set_markup(markup)
-
-        if self._fsZFWValue<0:
-            self._fsZFW.set_text("-")
-        else:
-            markup = WeightHelp._getMarkup(self._fsZFWValue,  zfw,
-                                           PayloadChecker.TOLERANCE)
-            self._fsZFW.set_markup("<b>" + markup + "</b>")
-
-        self._setWeightLabel(self._fsGross, self._fsGrossValue)
-
-    def _calculateWeights(self):
-        """Calculate the payload and the zero-fuel weight.
-
-        It returns a tuple with these two items. If any of the items cannot be
-        calculated, that is -1."""
-        if self._crew<0 or self._pax<0 or \
-               self._bag<0 or self._cargo<0 or self._mail<0:
-            payload = -1
-        else:
-            payload = (self._crew + self._pax) * self._humanWeight + \
-                      self._bag + self._cargo + self._mail
-
-        if payload<0 or self._dowValue<0:
-            zfw = -1
-        else:
-            zfw = payload + self._dowValue
-
-        return (payload, zfw)
-
-    def _usingHelpToggled(self, button):
-        """Called when the Using help button is toggled."""
-        assert self._usingHelp.get_active()
-        self._usingHelp.set_sensitive(False)
-
-        self._gui.logger.untimedMessage("The weight calculation help function was used by the pilot")
-
-        bookedFlight = self._gui.bookedFlight
-        self._crew = bookedFlight.numCrew
-        self._pax = bookedFlight.numPassengers
-        self._bag = bookedFlight.bagWeight
-        self._cargo = self._gui.cargoWeight
-        self._mail = bookedFlight.mailWeight
-        
-        aircraft = self._gui.flight.aircraft
-        self._humanWeight = aircraft.humanWeight
-        self._dowValue = aircraft.dow
-        self._mzfwValue = aircraft.mzfw
-        self._mtowValue = aircraft.mtow
-        self._mlwValue = aircraft.mlw
-
-        self._setupCalculated()
-        self._weightsTable.set_sensitive(True)
-
-    def _fsButtonClicked(self, button):
-        """Callback for the FS button being clicked."""
-        gui = self._gui
-        gui.beginBusy(xstr("weighthelp_busy"))
-        gui.simulator.requestWeights(self._handleWeights)
-
-    def _handleWeights(self, dow, payload, zfw, grossWeight):
-        """Handle the given weights."""
-        gobject.idle_add(self._processWeights, dow, payload, zfw, grossWeight)
-
-    def _processWeights(self, dow, payload, zfw, grossWeight):
-        """Process the given weights."""
-        self._gui.endBusy()
-        if self._usingHelp.get_active():
-            self._fsPayloadValue = payload
-            self._fsDOWValue = dow
-            self._fsZFWValue = zfw
-            self._fsGrossValue = grossWeight
-            self._setupFS()
-
-    def _setWeightLabel(self, label, weight, bold = False):
-        """Set the given weight label to the given weight."""
-        if weight<0:
-            label.set_text("-")
-        else:
-            markup = "%.0f" % (weight,)
-            if bold: markup = "<b>" + markup + "</b>"
-            label.set_markup(markup)
-                
-        
Index: src/mlx/i18n.py
===================================================================
--- src/mlx/i18n.py	(revision 155)
+++ src/mlx/i18n.py	(revision 155)
@@ -0,0 +1,1283 @@
+# Internationalization support
+# -*- coding: utf-8 -*-
+
+#------------------------------------------------------------------------------
+
+import locale
+
+#------------------------------------------------------------------------------
+
+_strings = None
+_fallback = None
+
+#------------------------------------------------------------------------------
+
+def setLanguage(language):
+    """Setup the internationalization support for the given language."""
+    print "i18n.setLanguage", language
+    _Strings.set(language)
+    
+#------------------------------------------------------------------------------
+
+def xstr(key):
+    """Get the string for the given key in the current language.
+
+    If not found, the fallback language is searched. If that is not found
+    either, the key itself is returned within curly braces."""
+    s = _Strings.current()[key]
+    if s is None:
+        s = _Strings.fallback()[key]
+    return "{" + key + "}" if s is None else s
+    
+#------------------------------------------------------------------------------
+
+class _Strings(object):
+    """A collection of strings belonging to a certain language."""
+    # The registry of the string collections. This is a mapping from
+    # language codes to the actual collection objects
+    _instances = {}
+
+    # The fallback instance
+    _fallback = None
+
+    # The currently used instance.
+    _current = None
+
+    @staticmethod
+    def _find(language):
+        """Find an instance for the given language.
+
+        First the language is searched for as is. If not found, it is truncated
+        from the end until the last underscore and that is searched for. And so
+        on, until no underscore remains.
+
+        If nothing is found this way, the 
+        """
+        while language:
+            if language in _Strings._instances:
+                return _Strings._instances[language]
+            underscoreIndex = language.rfind("_")
+            language = language[:underscoreIndex] if underscoreIndex>0 else ""
+        return _Strings._fallback
+
+    @staticmethod
+    def set(language):
+        """Set the string for the given language.
+
+        A String instance is searched for the given language (see
+        _Strings._find()). Otherwise the fallback language is used."""
+        strings = _Strings._find(language)
+        assert strings is not None
+        if _Strings._current is not None and \
+           _Strings._current is not _Strings._fallback:
+            _Strings._current.finalize()
+        if strings is not _Strings._fallback:
+            strings.initialize()
+        _Strings._current = strings
+
+    @staticmethod
+    def fallback():
+        """Get the fallback."""
+        return _Strings._fallback
+
+    @staticmethod
+    def current():
+        """Get the current."""
+        return _Strings._current
+
+    def __init__(self, languages, fallback = False):
+        """Construct an empty strings object."""
+        self._strings = {}
+        for language in languages:
+            _Strings._instances[language] = self
+        if fallback:
+            _Strings._fallback = self
+            self.initialize()
+
+    def initialize(self):
+        """Initialize the strings.
+
+        This should be overridden in children to setup the strings."""
+        pass
+
+    def finalize(self):
+        """Finalize the object.
+
+        This releases the string dictionary to free space."""
+        self._strings = {}
+
+    def add(self, id, s):
+        """Add the given text as the string with the given ID.
+
+        If the ID is already in the object, that is an assertion failure!"""
+        assert id not in self._strings
+        self._strings[id] = s
+
+    def __getitem__(self, key):
+        """Get the string  with the given key."""
+        return self._strings[key] if key in self._strings else None
+
+#------------------------------------------------------------------------------
+
+class _English(_Strings):
+    """The strings for the English language."""
+    def __init__(self):
+        """Construct the object."""
+        super(_English, self).__init__(["en_GB", "en"], fallback = True)
+
+    def initialize(self):
+        """Initialize the strings."""
+        self.add("button_ok", "_OK")
+        self.add("button_cancel", "_Cancel")
+        self.add("button_yes", "_Yes")
+        self.add("button_no", "_No")
+        self.add("button_browse", "Browse...")
+        
+        self.add("menu_file", "File")
+        self.add("menu_file_loadPIREP", "_Load PIREP...")
+        self.add("menu_file_loadPIREP_key", "l")
+        self.add("menu_file_quit", "_Quit")
+        self.add("menu_file_quit_key", "q")
+        self.add("quit_question", "Are you sure to quit the logger?")
+
+        self.add("menu_tools", "Tools")
+        self.add("menu_tools_prefs", "_Preferences")
+        self.add("menu_tools_prefs_key", "p")
+
+        self.add("menu_view", "View")
+        self.add("menu_view_monitor", "Show _monitor window")
+        self.add("menu_view_monitor_key", "m")
+        self.add("menu_view_debug", "Show _debug log")
+        self.add("menu_view_debug_key", "d")
+        
+        self.add("tab_flight", "_Flight")
+        self.add("tab_flight_tooltip", "Flight wizard")
+        self.add("tab_flight_info", "Flight _info")
+        self.add("tab_flight_info_tooltip", "Further information regarding the flight")
+        self.add("tab_weight_help", "_Help")
+        self.add("tab_weight_help_tooltip", "Help to calculate the weights")
+        self.add("tab_log", "_Log")
+        self.add("tab_log_tooltip",
+                 "The log of your flight that will be sent to the MAVA website")
+        self.add("tab_gates", "_Gates")        
+        self.add("tab_gates_tooltip", "The status of the MAVA fleet and the gates at LHBP")        
+        self.add("tab_debug_log", "_Debug log")
+        self.add("tab_debug_log_tooltip", "Log with debugging information.")
+
+        self.add("conn_failed", "Cannot connect to the simulator.")
+        self.add("conn_failed_sec",
+                 "Rectify the situation, and press <b>Try again</b> "
+                 "to try the connection again, " 
+                 "or <b>Cancel</b> to cancel the flight.")
+        self.add("conn_broken",
+                 "The connection to the simulator failed unexpectedly.")
+        self.add("conn_broken_sec",
+                 "If the simulator has crashed, restart it "
+                 "and restore your flight as much as possible "
+                 "to the state it was in before the crash. "
+                 "Then press <b>Reconnect</b> to reconnect.\n\n"
+                 "If you want to cancel the flight, press <b>Cancel</b>.")
+        self.add("button_tryagain", "_Try again")
+        self.add("button_reconnect", "_Reconnect")
+
+        self.add("login", "Login")
+        self.add("loginHelp",
+                 "Enter your MAVA pilot's ID and password to\n" \
+                 "log in to the MAVA website and download\n" \
+                 "your booked flights.")
+        self.add("label_pilotID", "Pil_ot ID:")
+        self.add("login_pilotID_tooltip",
+                 "Enter your MAVA pilot's ID. This usually starts with a "
+                 "'P' followed by 3 digits.")
+        self.add("label_password", "_Password:")
+        self.add("login_password_tooltip",
+                 "Enter the password for your pilot's ID")
+        self.add("remember_password", "_Remember password")
+        self.add("login_remember_tooltip",
+                 "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.")
+        self.add("button_login", "Logi_n")
+        self.add("login_button_tooltip", "Click to log in.")
+        self.add("login_busy", "Logging in...")
+        self.add("login_invalid", "Invalid pilot's ID or password.")
+        self.add("login_invalid_sec",
+                 "Check the ID and try to reenter the password.")
+        self.add("login_failconn",
+                 "Failed to connect to the MAVA website.")
+        self.add("login_failconn_sec", "Try again in a few minutes.")
+
+        self.add("button_next", "_Next")
+        self.add("button_next_tooltip", "Click to go to the next page.")
+        self.add("button_previous", "_Previous")
+        self.add("button_previous_tooltip", "Click to go to the previous page.")
+
+        self.add("flightsel_title", "Flight selection")
+        self.add("flightsel_help", "Select the flight you want to perform.")
+        self.add("flightsel_chelp", "You have selected the flight highlighted below.")
+        self.add("flightsel_no", "Flight no.")
+        self.add("flightsel_deptime", "Departure time [UTC]")
+        self.add("flightsel_from", "From")
+        self.add("flightsel_to", "To")
+
+        self.add("fleet_busy", "Retrieving fleet...")
+        self.add("fleet_failed",
+                 "Failed to retrieve the information on the fleet.")
+        self.add("fleet_update_busy", "Updating plane status...")
+        self.add("fleet_update_failed",
+                 "Failed to update the status of the airplane.")
+        
+        self.add("gatesel_title", "LHBP gate selection")
+        self.add("gatesel_help",
+                 "The airplane's gate position is invalid.\n\n" \
+                 "Select the gate from which you\n" \
+                 "would like to begin the flight.")
+        self.add("gatesel_conflict", "Gate conflict detected again.")
+        self.add("gatesel_conflict_sec",
+                 "Try to select a different gate.")
+
+        self.add("connect_title", "Connect to the simulator")
+        self.add("connect_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.")
+        self.add("connect_chelp",
+                 "The basic data of your flight can be read below.")
+        self.add("connect_flightno", "Flight number:")
+        self.add("connect_acft", "Aircraft:")
+        self.add("connect_tailno", "Tail number:")
+        self.add("connect_airport", "Airport:")
+        self.add("connect_gate", "Gate:")
+        self.add("button_connect", "_Connect")
+        self.add("button_connect_tooltip", "Click to connect to the simulator.")
+        self.add("connect_busy", "Connecting to the simulator...")
+
+        self.add("payload_title", "Payload")
+        self.add("payload_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.")
+        self.add("payload_chelp",
+                 "You can see the weights in the briefing\n" \
+                 "and the cargo weight you have selected below.\n\n" \
+                 "You can also query the ZFW reported by the simulator.")
+        self.add("payload_crew", "Crew:")
+        self.add("payload_pax", "Passengers:")
+        self.add("payload_bag", "Baggage:")
+        self.add("payload_cargo", "_Cargo:")
+        self.add("payload_cargo_tooltip",
+                 "The weight of the cargo for your flight.")
+        self.add("payload_mail", "Mail:")
+        self.add("payload_zfw", "Calculated ZFW:")
+        self.add("payload_fszfw", "_ZFW from FS:")
+        self.add("payload_fszfw_tooltip",
+                 "Click here to refresh the ZFW value from the simulator.")
+        self.add("payload_zfw_busy", "Querying ZFW...")
+
+        self.add("time_title", "Time")
+        self.add("time_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.")
+        self.add("time_chelp",
+                 "The departure and arrival times are displayed below in UTC.\n\n" \
+                 "You can also query the current UTC time from the simulator.\n")
+        self.add("time_departure", "Departure:")
+        self.add("time_arrival", "Arrival:")
+        self.add("time_fs", "_Time from FS:")
+        self.add("time_fs_tooltip",
+                 "Click here to query the current UTC time from the simulator.")
+        self.add("time_busy", "Querying time...")
+
+        self.add("fuel_title", "Fuel")
+        self.add("fuel_help",
+                 "Enter the amount of fuel in kilograms that need to be "
+                 "present in each tank below.\n\n"
+                 "When you press <b>Next</b>, the necessary amount of fuel\n"
+                 "will be pumped into or out of the tanks.")
+        self.add("fuel_chelp",
+                 "The amount of fuel tanked into your aircraft at the\n"
+                 "beginning of the flight can be seen below.")
+
+        self.add("fuel_tank_centre", "_Centre\n")
+        self.add("fuel_tank_left", "L_eft\n")
+        self.add("fuel_tank_right", "_Right\n")
+        self.add("fuel_tank_left_aux", "Left\nA_ux")
+        self.add("fuel_tank_right_aux", "Right\nAu_x")
+        self.add("fuel_tank_left_tip", "Left\n_Tip")
+        self.add("fuel_tank_right_tip", "Right\nTip")
+        self.add("fuel_tank_external1", "External\n_1")
+        self.add("fuel_tank_external2", "External\n_2")
+        self.add("fuel_tank_centre2", "Ce_ntre\n2")
+        self.add("fuel_get_busy", "Querying fuel information...")
+        self.add("fuel_pump_busy", "Pumping fuel...")
+        self.add("fuel_tank_tooltip",
+                 "This part displays the current level of the fuel in the "
+                 "compared to its capacity. The "
+                 '<span color="turquoise">turquoise</span> '
+                 "slider shows the level that should be loaded into the tank "
+                 "for the flight. You can click anywhere in the widget to "
+                 "move the slider there. Or you can grab it by holding down "
+                 "the left button of your mouse, and move the pointer up or "
+                 "down. The scroll wheel on your mouse also increments or "
+                 "decrements the amount of fuel by 10. If you hold down "
+                 "the <b>Shift</b> key while scrolling, the steps will be "
+                 "100, or with the <b>Control</b> key, 1.")
+
+        self.add("route_title", "Route")
+        self.add("route_help",
+                 "Set your cruise flight level below, and\n" \
+                 "if necessary, edit the flight plan.")
+        self.add("route_chelp",
+                 "If necessary, you can modify the cruise level and\n" \
+                 "the flight plan below during flight.\n" \
+                 "If so, please, add a comment on why " \
+                 "the modification became necessary.")
+        self.add("route_level", "_Cruise level:")
+        self.add("route_level_tooltip",
+                 "The cruise flight level. Click on the arrows to increment "
+                 "or decrement by 10, or enter the number on the keyboard.")
+        self.add("route_route", "_Route")
+        self.add("route_route_tooltip",
+                 "The planned flight route in the standard format.")
+        self.add("route_down_notams", "Downloading NOTAMs...")
+        self.add("route_down_metars", "Downloading METARs...")
+
+        self.add("briefing_title", "Briefing (%d/2): %s")
+        self.add("briefing_departure", "departure")
+        self.add("briefing_arrival", "arrival")
+        self.add("briefing_help",
+                 "Read carefully the NOTAMs and METAR below.\n\n" \
+                 "You can edit the METAR if your simulator or network\n" \
+                 "provides different weather.")
+        self.add("briefing_chelp",
+                 "If your simulator or network provides a different\n" \
+                 "weather, you can edit the METAR below.")
+        self.add("briefing_notams_init", "LHBP NOTAMs")
+        self.add("briefing_metar_init", "LHBP METAR")
+        self.add("briefing_button",
+                 "I have read the briefing and am _ready to fly!")
+        self.add("briefing_notams_template", "%s NOTAMs")
+        self.add("briefing_metar_template", "%s _METAR")
+        self.add("briefing_notams_failed", "Could not download NOTAMs")
+        self.add("briefing_notams_missing",
+                 "Could not download NOTAM for this airport")
+        self.add("briefing_metar_failed", "Could not download METAR")
+
+        self.add("takeoff_title", "Takeoff")
+        self.add("takeoff_help",
+                 "Enter the runway and SID used, as well as the speeds.")
+        self.add("takeoff_chelp",
+                 "The runway, SID and takeoff speeds logged can be seen below.")
+        self.add("takeoff_runway", "Run_way:")
+        self.add("takeoff_runway_tooltip",
+                 "The runway the takeoff is performed from.")
+        self.add("takeoff_sid", "_SID:")
+        self.add("takeoff_sid_tooltip",
+                 "The name of the Standard Instrument Deparature procedure followed.")
+        self.add("takeoff_v1", "V<sub>_1</sub>:")
+        self.add("takeoff_v1_tooltip", "The takeoff decision speed in knots.")
+        self.add("label_knots", "knots")
+        self.add("takeoff_vr", "V<sub>_R</sub>:")
+        self.add("takeoff_vr_tooltip", "The takeoff rotation speed in knots.")
+        self.add("takeoff_v2", "V<sub>_2</sub>:")
+        self.add("takeoff_v2_tooltip", "The takeoff safety speed in knots.")
+
+        self.add("landing_title", "Landing")
+        self.add("landing_help",
+                 "Enter the STAR and/or transition, runway,\n" \
+                 "approach type and V<sub>Ref</sub> used.")
+        self.add("landing_chelp",
+                 "The STAR and/or transition, runway, approach\n" \
+                 "type and V<sub>Ref</sub> logged can be seen below.")
+        self.add("landing_star", "_STAR:")
+        self.add("landing_star_tooltip",
+                 "The name of Standard Terminal Arrival Route followed.")
+        self.add("landing_transition", "_Transition:")
+        self.add("landing_transition_tooltip",
+                 "The name of transition executed or VECTORS if vectored by ATC.")
+        self.add("landing_runway", "Run_way:")
+        self.add("landing_runway_tooltip",
+                 "The runway the landing is performed on.")
+        self.add("landing_approach", "_Approach type:")
+        self.add("landing_approach_tooltip",
+                 "The type of the approach, e.g. ILS or VISUAL.")
+        self.add("landing_vref", "V<sub>_Ref</sub>:")
+        self.add("landing_vref_tooltip",
+                 "The landing reference speed in knots.")
+
+        self.add("flighttype_scheduled", "scheduled")
+        self.add("flighttype_ot", "old-timer")
+        self.add("flighttype_vip", "VIP")
+        self.add("flighttype_charter", "charter")
+
+        self.add("finish_title", "Finish")
+        self.add("finish_help",
+                 "There are some statistics about your flight below.\n\n" \
+                 "Review the data, also on earlier pages, and if you are\n" \
+                 "satisfied, you can save or send your PIREP.")
+        self.add("finish_rating", "Flight rating:")
+        self.add("finish_flight_time", "Flight time:")
+        self.add("finish_block_time", "Block time:")
+        self.add("finish_distance", "Distance flown:")
+        self.add("finish_fuel", "Fuel used:")
+        self.add("finish_type", "_Type:")
+        self.add("finish_type_tooltip", "Select the type of the flight.")
+        self.add("finish_online", "_Online flight")
+        self.add("finish_online_tooltip",
+                 "Check if your flight was online, uncheck otherwise.")
+        self.add("finish_gate", "_Arrival gate:")
+        self.add("finish_gate_tooltip",
+                 "Select the gate or stand at which you have arrived to LHBP.")
+        self.add("finish_save", "Sa_ve PIREP...")
+        self.add("finish_save_tooltip",
+                 "Click to save the PIREP into a file on your computer. " \
+                 "The PIREP can be loaded and sent later.")
+        self.add("finish_save_title", "Save PIREP into")
+        self.add("finish_save_done", "The PIREP was saved successfully")
+        self.add("finish_save_failed", "Failed to save the PIREP")
+        self.add("finish_save_failed_sec", "See the debug log for the details.")
+
+        # C D
+        
+        self.add("info_comments", "_Comments")
+        self.add("info_defects", "Flight _defects")
+        self.add("info_delay", "Delay codes")
+
+        # O V N E Y T R A W P
+        
+        self.add("info_delay_loading", "L_oading problems")
+        self.add("info_delay_vatsim", "_VATSIM problem")
+        self.add("info_delay_net", "_Net problems")
+        self.add("info_delay_atc", "Controll_er's fault")
+        self.add("info_delay_system", "S_ystem crash/freeze")
+        self.add("info_delay_nav", "Naviga_tion problem")
+        self.add("info_delay_traffic", "T_raffic problems")
+        self.add("info_delay_apron", "_Apron navigation problem")
+        self.add("info_delay_weather", "_Weather problems")
+        self.add("info_delay_personal", "_Personal reasons")
+
+        self.add("statusbar_conn_tooltip",
+                 '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.')
+        self.add("statusbar_stage_tooltip", "The flight stage")
+        self.add("statusbar_time_tooltip", "The simulator time in UTC")
+        self.add("statusbar_rating_tooltip", "The flight rating")
+        self.add("statusbar_busy_tooltip", "The status of the background tasks.")
+
+        self.add("flight_stage_boarding", "boarding")
+        self.add("flight_stage_pushback and taxi", "pushback and taxi")
+        self.add("flight_stage_takeoff", "takeoff")
+        self.add("flight_stage_RTO", "RTO")
+        self.add("flight_stage_climb", "climb")
+        self.add("flight_stage_cruise", "cruise")
+        self.add("flight_stage_descent", "descent")
+        self.add("flight_stage_landing", "landing")
+        self.add("flight_stage_taxi", "taxi")
+        self.add("flight_stage_parking", "parking")
+        self.add("flight_stage_go-around", "go-around")
+        self.add("flight_stage_end", "end")
+
+        self.add("statusicon_showmain", "Show main window")
+        self.add("statusicon_showmonitor", "Show monitor window")
+        self.add("statusicon_quit", "Quit")
+        self.add("statusicon_stage", "Stage")
+        self.add("statusicon_rating", "Rating")
+
+        self.add("update_title", "Update")
+        self.add("update_needsudo",
+                 "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.")
+        self.add("update_manifest_progress", "Downloading manifest...")
+        self.add("update_manifest_done", "Downloaded manifest...")
+        self.add("update_files_progress", "Downloading files...")
+        self.add("update_files_bytes", "Downloaded %d of %d bytes")
+        self.add("update_renaming", "Renaming downloaded files...")
+        self.add("update_renamed", "Renamed %s")
+        self.add("update_removing", "Removing files...")
+        self.add("update_removed", "Removed %s")
+        self.add("update_writing_manifest", "Writing the new manifest")
+        self.add("update_finished",
+                 "Finished updating. Press OK to restart the program.")
+        self.add("update_nothing", "There was nothing to update")
+        self.add("update_failed", "Failed, see the debug log for details.")
+
+        self.add("weighthelp_usinghelp", "_Using help")
+        self.add("weighthelp_usinghelp_tooltip",
+                 "If you check this, some help will be displayed on how "
+                 "to calculate the payload weight for your flight. "
+                 "Note, that the usage of this facility will be logged.")
+        self.add("weighthelp_header_calculated", "Requested/\ncalculated")
+        self.add("weighthelp_header_simulator", "Simulator\ndata")
+        self.add("weighthelp_header_simulator_tooltip",
+                 "Click this button to retrieve the weight values from the "
+                 "simulator, which will be displayed below. If a value is "
+                 "within 10% of the tolerance, it is displayed in "
+                 '<b><span foreground="darkgreen">green</span></b>, '
+                 "if it is out of the tolerance, it is displayed in "
+                 '<b><span foreground="red">red</span></b>, '
+                 "otherwise in"
+                 '<b><span foreground="orange">yellow</span></b>.')
+        self.add("weighthelp_crew", "Crew (%s):")
+        self.add("weighthelp_pax", "Passengers (%s):")
+        self.add("weighthelp_baggage", "Baggage:")
+        self.add("weighthelp_cargo", "Cargo:")
+        self.add("weighthelp_mail", "Mail:")
+        self.add("weighthelp_payload", "Payload:")
+        self.add("weighthelp_dow", "DOW:")
+        self.add("weighthelp_zfw", "ZFW:")
+        self.add("weighthelp_gross", "Gross weight:")
+        self.add("weighthelp_mzfw", "MZFW:")
+        self.add("weighthelp_mtow", "MTOW:")
+        self.add("weighthelp_mlw", "MLW:")
+        self.add("weighthelp_busy", "Querying weight data...")
+
+        self.add("gates_fleet_title", "Fl_eet")
+        self.add("gates_gates_title", "LHBP gates")
+        self.add("gates_tailno", "Tail nr.")
+        self.add("gates_planestatus", "Status")
+        self.add("gates_refresh", "_Refresh data")
+        self.add("gates_refresh_tooltip",
+                 "Click this button to refresh the status data above")
+        self.add("gates_planes_tooltip",
+                 "This table lists all the planes in the MAVA fleet and their "
+                 "last known location. If a plane is conflicting with another "
+                 "because of occupying the same gate its data is displayed in "
+                 "<b><span foreground=\"red\">red</span></b>.")
+        self.add("gates_gates_tooltip",
+                 "The numbers of the gates occupied by MAVA planes are "
+                 "displayed in "
+                 '<b><span foreground="orange">yellow</span></b>, '
+                 "while available gates in black.")
+        self.add("gates_plane_away", "AWAY")
+        self.add("gates_plane_parking", "PARKING")
+        self.add("gates_plane_unknown", "UNKNOWN")
+
+        self.add("prefs_title", "Preferences")
+        self.add("prefs_tab_general", "_General")
+        self.add("prefs_tab_general_tooltip", "General preferences")
+        self.add("prefs_tab_messages", "_Messages")
+        self.add("prefs_tab_message_tooltip",
+                 "Enable/disable message notifications in FS and/or by sound")
+        self.add("prefs_tab_advanced", "_Advanced")
+        self.add("prefs_tab_advanced_tooltip",
+                 "Advanced preferences, edit with care!")
+        self.add("prefs_language", "_Language:")
+        self.add("prefs_language_tooltip",
+                 "The language of the program")
+        self.add("prefs_restart",
+                 "Restart needed")
+        self.add("prefs_language_restart_sec",
+                 "If you change the language, the program should be restarted "
+                 "so that the change has an effect.")
+        self.add("prefs_lang_$system", "system default")
+        self.add("prefs_lang_en_GB", "English")
+        self.add("prefs_lang_hu_HU", "Hungarian")
+        self.add("prefs_hideMinimizedWindow",
+                 "_Hide the main window when minimized")
+        self.add("prefs_hideMinimizedWindow_tooltip",
+                 "If checked, the main window will be hidden completely "
+                 "when minimized. You can still make it appear by "
+                 "clicking on the status icon or using its popup menu.")
+        self.add("prefs_onlineGateSystem",
+                 "_Use the Online Gate System")
+        self.add("prefs_onlineGateSystem_tooltip",
+                 "If this is checked, the logger will query and update the "
+                 "LHBP Online Gate System.")
+        self.add("prefs_onlineACARS",
+                 "Use the Online ACA_RS System")
+        self.add("prefs_onlineACARS_tooltip",
+                 "If this is checked, the logger will continuously update "
+                 "the MAVA Online ACARS System with your flight's data.")
+        self.add("prefs_flaretimeFromFS",
+                 "Take flare _time from the simulator")
+        self.add("prefs_flaretimeFromFS_tooltip",
+                 "If this is checked, the time of the flare will be calculated "
+                 "from timestamps returned by the simulator.")
+        self.add("prefs_syncFSTime",
+                 "_Synchronize the time in FS with the computer's clock")
+        self.add("prefs_syncFSTime_tooltip",
+                 "If this is checked the flight simulator's internal clock "
+                 "will always be synchronized to the computer's clock.")
+        self.add("prefs_pirepDirectory",
+                 "_PIREP directory:")
+        self.add("prefs_pirepDirectory_tooltip",
+                 "The directory that will be offered by default when "
+                 "saving a PIREP.")
+        self.add("prefs_pirepDirectory_browser_title",
+                 "Select PIREP directory")
+        self.add("prefs_update_auto", "Update the program auto_matically")
+        self.add("prefs_update_auto_tooltip",
+                 "If checked the program will look for updates when "
+                 "it is starting, and if new updates are found, they "
+                 "will be downloaded and installed. This ensures that "
+                 "the PIREP you send will always conform to the latest "
+                 "expectations of the airline.")
+        self.add("prefs_update_auto_warning",
+                 "Disabling automatic updates may result in "
+                 "your version of the program becoming out of date "
+                 "and your PIREPs not being accepted.")
+        self.add("prefs_update_url", "Update _URL:")
+        self.add("prefs_update_url_tooltip",
+                 "The URL from which to download the updates. Change this "
+                 "only if you know what you are doing!")
+
+        # A C G M O S
+        
+        self.add("prefs_msgs_fs", "Displayed in FS")
+        self.add("prefs_msgs_sound", "Sound alert")
+        self.add("prefs_msgs_type_loggerError", "Logger _Error Messages")
+        self.add("prefs_msgs_type_information",
+                 "_Information Messages\n(e.g. flight status)")
+        self.add("prefs_msgs_type_fault",
+                 "_Fault Messages\n(e.g. strobe light fault)")
+        self.add("prefs_msgs_type_nogo",
+                 "_NOGO Fault messages\n(e.g. MTOW NOGO)")
+        self.add("prefs_msgs_type_gateSystem",
+                 "Ga_te System Messages\n(e.g. available gates)")
+        self.add("prefs_msgs_type_environment",
+                 "Envi_ronment Messages\n(e.g. \"welcome to XY aiport\")")
+        self.add("prefs_msgs_type_help",
+                 "_Help Messages\n(e.g. \"don't forget to set VREF\")")
+        self.add("prefs_msgs_type_visibility",
+                 "_Visibility Messages")
+
+        self.add("loadPIREP_browser_title", "Select the PIREP to load")
+        self.add("loadPIREP_filter_pireps", "PIREP files")
+        self.add("loadPIREP_filter_all", "All files")
+        self.add("loadPIREP_failed", "Failed to load the PIREP")
+        self.add("loadPIREP_failed_sec", "See the debug log for the details.")
+        self.add("loadPIREP_send_title", "PIREP")
+        self.add("loadPIREP_send_help",
+                 "The main data of the PIREP loaded:")
+        self.add("loadPIREP_send_flightno", "Flight number:")
+        self.add("loadPIREP_send_date", "Date:")
+        self.add("loadPIREP_send_from", "From:")
+        self.add("loadPIREP_send_to", "To:")
+        self.add("loadPIREP_send_rating", "Rating:")
+        
+        self.add("sendPIREP", "_Send PIREP...")
+        self.add("sendPIREP_tooltip",
+                 "Click to send the PIREP to the MAVA website for further review.")
+        self.add("sendPIREP_busy", "Sending PIREP...")
+        self.add("sendPIREP_success",
+                 "The PIREP was sent successfully.")
+        self.add("sendPIREP_success_sec",
+                 "Await the thorough scrutiny by our fearless PIREP reviewers! :)")
+        self.add("sendPIREP_already",
+                 "The PIREP for this flight has already been sent!")
+        self.add("sendPIREP_already_sec",
+                 "You may clear the old PIREP on the MAVA website.")
+        self.add("sendPIREP_notavail",
+                 "This flight is not available anymore!")
+        self.add("sendPIREP_unknown",
+                 "The MAVA website returned with an unknown error.")
+        self.add("sendPIREP_unknown_sec",
+                 "See the debug log for more information.")
+        self.add("sendPIREP_failed",
+                 "Could not send the PIREP to the MAVA website.")
+        self.add("sendPIREP_failed_sec",
+                 "This can be a network problem, in which case\n" \
+                 "you may try again later. Or it can be a bug;\n" \
+                 "see the debug log for more information.")
+
+#------------------------------------------------------------------------------
+
+class _Hungarian(_Strings):
+    """The strings for the Hungarian language."""
+    def __init__(self):
+        """Construct the object."""
+        super(_Hungarian, self).__init__(["hu_HU", "hu"])
+
+    def initialize(self):
+        """Initialize the strings."""
+        self.add("button_ok", "_OK")
+        self.add("button_cancel", "_Mégse")
+        self.add("button_yes", "_Igen")
+        self.add("button_no", "_Nem")
+        self.add("button_browse", "Keresés...")
+        
+        self.add("menu_file", "Fájl")
+        self.add("menu_file_loadPIREP", "PIREP be_töltése...")
+        self.add("menu_file_loadPIREP_key", "t")
+        self.add("menu_file_quit", "_Kilépés")
+        self.add("menu_file_quit_key", "k")
+        self.add("quit_question", "Biztosan ki akarsz lépni?")
+
+        self.add("menu_tools", "Eszközök")
+        self.add("menu_tools_prefs", "_Beállítások")
+        self.add("menu_tools_prefs_key", "b")
+
+        self.add("menu_view", "Nézet")
+        self.add("menu_view_monitor", "Mutasd a _monitor ablakot")
+        self.add("menu_view_monitor_key", "m")
+        self.add("menu_view_debug", "Mutasd a _debug naplót")
+        self.add("menu_view_debug_key", "d")
+        
+        self.add("tab_flight", "_Járat")
+        self.add("tab_flight_tooltip", "Járat varázsló")
+        self.add("tab_flight_info", "Járat _info")
+        self.add("tab_flight_info_tooltip", "Egyéb információk a járat teljesítésével kapcsolatban")
+        self.add("tab_weight_help", "_Segítség")
+        self.add("tab_weight_help_tooltip", "Segítség a súlyszámításhoz")
+        self.add("tab_log", "_Napló")
+        self.add("tab_log_tooltip",
+                 "A járat naplója, amit majd el lehet küldeni a MAVA szerverére")
+        self.add("tab_gates", "_Kapuk")        
+        self.add("tab_gates_tooltip", "A MAVA flotta és LHBP kapuinak állapota")        
+        self.add("tab_debug_log", "_Debug napló")
+        self.add("tab_debug_log_tooltip",
+                 "Hibakereséshez használható információkat tartalmazó napló.")
+
+        self.add("conn_failed", "Nem tudtam kapcsolódni a szimulátorhoz.")
+        self.add("conn_failed_sec",
+                 "Korrigáld a problémát, majd nyomd meg az "
+                 "<b>Próbáld újra</b> gombot a újrakapcsolódáshoz, "
+                 "vagy a <b>Mégse</b> gombot a járat megszakításához.")
+        self.add("conn_broken",
+                 "A szimulátorral való kapcsolat váratlanul megszakadt.")
+        self.add("conn_broken_sec",
+                 "Ha a szimulátor elszállt, indítsd újra "
+                 "és állítsd vissza a repülésed elszállás előtti "
+                 "állapotát amennyire csak lehet. "
+                 "Ezután nyomd meg az <b>Újrakapcsolódás</b> gombot "
+                 "a kapcsolat ismételt felvételéhez.\n\n"
+                 "Ha meg akarod szakítani a repülést, nyomd meg a "
+                 "<b>Mégse</b> gombot.")
+        self.add("button_tryagain", "_Próbáld újra")
+        self.add("button_reconnect", "Újra_kapcsolódás")
+
+        self.add("login", "Bejelentkezés")
+        self.add("loginHelp",
+                 "Írd be a MAVA pilóta azonosítódat és a\n" \
+                 "bejelentkezéshez használt jelszavadat,\n" \
+                 "hogy választhass a foglalt járataid közül.")
+        self.add("label_pilotID", "_Azonosító:")
+        self.add("login_pilotID_tooltip",
+                 "Írd be a MAVA pilóta azonosítódat. Ez általában egy 'P' "
+                 "betűvel kezdődik, melyet 3 számjegy követ.")
+        self.add("label_password", "Je_lszó:")
+        self.add("login_password_tooltip",
+                 "Írd be a pilóta azonosítódhoz tartozó jelszavadat.")
+        self.add("remember_password", "_Emlékezz a jelszóra")
+        self.add("login_remember_tooltip",
+                 "Ha ezt kiválasztod, a jelszavadat eltárolja a program, így "
+                 "nem kell mindig újból beírnod. Vedd azonban figyelembe, "
+                 "hogy a jelszót szövegként tároljuk, így bárki elolvashatja, "
+                 "aki hozzáfér a fájljaidhoz.")
+        self.add("button_login", "_Bejelentkezés")
+        self.add("login_button_tooltip", "Kattints ide a bejelentkezéshez.")
+        self.add("login_busy", "Bejelentkezés...")
+        self.add("login_invalid", "Érvénytelen azonosító vagy jelszó.")
+        self.add("login_invalid_sec",
+                 "Ellenőrízd az azonosítót, és próbáld meg újra beírni a jelszót.")
+        self.add("login_failconn",
+                 "Nem sikerült kapcsolódni a MAVA honlaphoz.")
+        self.add("login_failconn_sec", "Próbáld meg pár perc múlva.")
+        
+        self.add("button_next", "_Előre")
+        self.add("button_next_tooltip",
+                 "Kattints ide, hogy a következő lapra ugorj.")
+        self.add("button_previous", "_Vissza")
+        self.add("button_previous_tooltip",
+                 "Kattints ide, hogy az előző lapra ugorj.")
+
+        self.add("flightsel_title", "Járatválasztás")
+        self.add("flightsel_help", "Válaszd ki a járatot, amelyet le szeretnél repülni.")
+        self.add("flightsel_chelp", "A lent kiemelt járatot választottad.")
+        self.add("flightsel_no", "Járatszám")
+        self.add("flightsel_deptime", "Indulás ideje [UTC]")
+        self.add("flightsel_from", "Honnan")
+        self.add("flightsel_to", "Hová")
+
+        self.add("fleet_busy", "Flottaadatok letöltése...")
+        self.add("fleet_failed",
+                 "Nem sikerült letöltenem a flotta adatait.")
+        self.add("fleet_update_busy", "Repülőgép pozíció frissítése...")
+        self.add("fleet_update_failed",
+                 "Nem sikerült frissítenem a repülőgép pozícióját.")
+
+        self.add("gatesel_title", "LHBP kapuválasztás")
+        self.add("gatesel_help",
+                 "A repülőgép kapu pozíciója érvénytelen.\n\n" \
+                 "Válaszd ki azt a kaput, ahonnan\n" \
+                 "el szeretnéd kezdeni a járatot.")
+        self.add("gatesel_conflict", "Ismét kapuütközés történt.")
+        self.add("gatesel_conflict_sec",
+                 "Próbálj egy másik kaput választani.")
+
+        self.add("connect_title", "Kapcsolódás a szimulátorhoz")
+        self.add("connect_help",
+                 "Tölsd be a lent látható repülőgépet a szimulátorba\n" \
+                 "az alább megadott reptérre és kapuhoz.\n\n" \
+                 "Ezután nyomd meg a Kapcsolódás gombot a kapcsolódáshoz.")
+        self.add("connect_chelp",
+                 "A járat alapadatai lent olvashatók.")
+        self.add("connect_flightno", "Járatszám:")
+        self.add("connect_acft", "Típus:")
+        self.add("connect_tailno", "Lajstromjel:")
+        self.add("connect_airport", "Repülőtér:")
+        self.add("connect_gate", "Kapu:")
+        self.add("button_connect", "K_apcsolódás")
+        self.add("button_connect_tooltip",
+                 "Kattints ide a szimulátorhoz való kapcsolódáshoz.")
+        self.add("connect_busy", "Kapcsolódás a szimulátorhoz...")
+
+        self.add("payload_title", "Terhelés")
+        self.add("payload_help",
+                 "Az eligazítás az alábbi tömegeket tartalmazza.\n" \
+                 "Allítsd be a teherszállítmány tömegét itt, a hasznos "
+                 "terhet pedig a szimulátorban.\n\n" \
+                 "Azt is ellenőrízheted, hogy a szimulátor milyen ZFW-t jelent.")
+        self.add("payload_chelp",
+                 "Lent láthatók az eligazításban szereplő tömegek, valamint\n" \
+                 "a teherszállítmány általad megadott tömege.\n\n" \
+                 "Azt is ellenőrízheted, hogy a szimulátor milyen ZFW-t jelent.")
+        self.add("payload_crew", "Legénység:")
+        self.add("payload_pax", "Utasok:")
+        self.add("payload_bag", "Poggyász:")
+        self.add("payload_cargo", "_Teher:")
+        self.add("payload_cargo_tooltip",
+                 "A teherszállítmány tömege.")
+        self.add("payload_mail", "Posta:")
+        self.add("payload_zfw", "Kiszámolt ZFW:")
+        self.add("payload_fszfw", "_ZFW a szimulátorból:")
+        self.add("payload_fszfw_tooltip",
+                 "Kattints ide, hogy frissítsd a ZFW értékét a szimulátorból.")
+        self.add("payload_zfw_busy", "ZFW lekérdezése...")
+        
+        self.add("time_title", "Menetrend")
+        self.add("time_help",
+                 "Az indulás és az érkezés ideje lent látható UTC szerint.\n\n" \
+                 "A szimulátor aktuális UTC szerinti idejét is lekérdezheted.\n" \
+                 "Győzödj meg arról, hogy elég időd van a repülés előkészítéséhez.")
+        self.add("time_chelp",
+                 "Az indulás és az érkezés ideje lent látható UTC szerint.\n\n" \
+                 "A szimulátor aktuális UTC szerinti idejét is lekérdezheted.")
+        self.add("time_departure", "Indulás:")
+        self.add("time_arrival", "Érkezés:")
+        self.add("time_fs", "Idő a s_zimulátorból:")
+        self.add("time_fs_tooltip",
+                 "Kattings ide, hogy frissítsd a szimulátor aktuális UTC szerint idejét.")
+        self.add("time_busy", "Idő lekérdezése...")
+
+        self.add("fuel_title", "Üzemanyag")
+        self.add("fuel_help",
+                 "Írd be az egyes tartályokba szükséges üzemanyag "
+                 "mennyiségét kilogrammban.\n\n"
+                 "Ha megnyomod az <b>Előre</b> gombot, a megadott mennyiségű\n"
+                 "üzemanyag bekerül a tartályokba.")
+        self.add("fuel_chelp",
+                 "A repülés elején az egyes tartályokba tankolt\n"
+                 "üzemanyag mennyisége lent látható.")
+
+        # A B D E I G J K N O P S T V Y Z
+        
+        self.add("fuel_tank_centre", "Kö_zépső\n")
+        self.add("fuel_tank_left", "_Bal\n")
+        self.add("fuel_tank_right", "J_obb\n")
+        self.add("fuel_tank_left_aux", "Bal\nkie_gészítő")
+        self.add("fuel_tank_right_aux", "Jobb\nkiegészí_tő")
+        self.add("fuel_tank_left_tip", "B_al\nszárnyvég")
+        self.add("fuel_tank_right_tip", "Jobb\nszárn_yvég")
+        self.add("fuel_tank_external1", "Külső\n_1")
+        self.add("fuel_tank_external2", "Külső\n_2")
+        self.add("fuel_tank_centre2", "Közé_pső\n2")
+        self.add("fuel_get_busy", "Az üzemanyag lekérdezése...")
+        self.add("fuel_pump_busy", "Az üzemanyag pumpálása...")
+        self.add("fuel_tank_tooltip",
+                 "Ez mutatja az üzemanyag szintjét a tartályban annak "
+                 "kapacitásához mérve. A "
+                 '<span color="turquoise">türkizkék</span> '
+                 "csúszka mutatja a repüléshez kívánt szintet. "
+                 "Ha a bal gombbal bárhová kattintasz az ábrán, a csúszka "
+                 "odaugrik. Ha a gombot lenyomva tartod, és az egérmutatót "
+                 "föl-le mozgatod, a csúszka követi azt. Az egered görgőjével "
+                 "is kezelheted a csúszkát. Alaphelyzetben az üzemanyag "
+                 "mennyisége 10-zel nő, illetve csökken a görgetés irányától "
+                 "függően. Ha a <b>Shift</b> billentyűt lenyomva tartod, "
+                 "növekmény 100, a <b>Control</b> billentyűvel pedig 1 lesz.")
+
+        self.add("route_title", "Útvonal")
+        self.add("route_help",
+                 "Állítsd be az utazószintet lent, és ha szükséges,\n" \
+                 "módosítsd az útvonaltervet.")
+        self.add("route_chelp",
+                 "Ha szükséges, lent módosíthatod az utazószintet és\n" \
+                 "az útvonaltervet repülés közben is.\n" \
+                 "Ha így teszel, légy szíves a megjegyzés mezőben " \
+                 "ismertesd ennek okát.")
+        self.add("route_level", "_Utazószint:")
+        self.add("route_level_tooltip", "Az utazószint.")
+        self.add("route_route", "Út_vonal")
+        self.add("route_route_tooltip", "Az útvonal a szokásos formátumban.")
+        self.add("route_down_notams", "NOTAM-ok letöltése...")
+        self.add("route_down_metars", "METAR-ok letöltése...")
+        
+        self.add("briefing_title", "Eligazítás (%d/2): %s")
+        self.add("briefing_departure", "indulás")
+        self.add("briefing_arrival", "érkezés")
+        self.add("briefing_help",
+                 "Olvasd el figyelmesen a lenti NOTAM-okat és METAR-t.\n\n" \
+                 "Ha a szimulátor vagy hálózat más időjárást ad,\n" \
+                 "a METAR-t módosíthatod.")
+        self.add("briefing_chelp",
+                 "Ha a szimulátor vagy hálózat más időjárást ad,\n" \
+                 "a METAR-t módosíthatod.")
+        self.add("briefing_notams_init", "LHBP NOTAM-ok")
+        self.add("briefing_metar_init", "LHBP METAR")
+        self.add("briefing_button",
+                 "Elolvastam az eligazítást, és készen állok a _repülésre!")
+        self.add("briefing_notams_template", "%s NOTAM-ok")
+        self.add("briefing_metar_template", "%s _METAR")
+        self.add("briefing_notams_failed", "Nem tudtam letölteni a NOTAM-okat.")
+        self.add("briefing_notams_missing",
+                 "Ehhez a repülőtérhez nem találtam NOTAM-ot.")
+        self.add("briefing_metar_failed", "Nem tudtam letölteni a METAR-t.")
+        
+        self.add("takeoff_title", "Felszállás")
+        self.add("takeoff_help",
+                 "Írd be a felszállásra használt futópálya és SID nevét, valamint a sebességeket.")
+        self.add("takeoff_chelp",
+                 "A naplózott futópálya, SID és a sebességek lent olvashatók.")
+        self.add("takeoff_runway", "_Futópálya:")
+        self.add("takeoff_runway_tooltip",
+                 "A felszállásra használt futópálya.")
+        self.add("takeoff_sid", "_SID:")
+        self.add("takeoff_sid_tooltip",
+                 "Az alkalmazott szabványos műszeres indulási eljárás neve.")
+        self.add("takeoff_v1", "V<sub>_1</sub>:")
+        self.add("takeoff_v1_tooltip", "Az elhatározási sebesség csomóban.")
+        self.add("label_knots", "csomó")
+        self.add("takeoff_vr", "V<sub>_R</sub>:")
+        self.add("takeoff_vr_tooltip", "Az elemelkedési sebesség csomóban.")
+        self.add("takeoff_v2", "V<sub>_2</sub>:")
+        self.add("takeoff_v2_tooltip", "A biztonságos emelkedési sebesség csomóban.")
+        
+        self.add("landing_title", "Leszállás")
+        self.add("landing_help",
+                 "Írd be az alkalmazott STAR és/vagy bevezetési eljárás nevét,\n"
+                 "a használt futópályát, a megközelítés módját, és a V<sub>Ref</sub>-et.")
+        self.add("landing_chelp",
+                 "Az alkalmazott STAR és/vagy bevezetési eljárás neve, a használt\n"
+                 "futópálya, a megközelítés módja és a V<sub>Ref</sub> lent olvasható.")
+        self.add("landing_star", "_STAR:")
+        self.add("landing_star_tooltip",
+                 "A követett szabványos érkezési eljárás neve.")
+        self.add("landing_transition", "_Bevezetés:")
+        self.add("landing_transition_tooltip",
+                 "Az alkalmazott bevezetési eljárás neve, vagy VECTORS, "
+                 "ha az irányítás vezetett be.")
+        self.add("landing_runway", "_Futópálya:")
+        self.add("landing_runway_tooltip",
+                 "A leszállásra használt futópálya.")
+        self.add("landing_approach", "_Megközelítés típusa:")
+        self.add("landing_approach_tooltip",
+                 "A megközelítgés típusa, pl. ILS vagy VISUAL.")
+        self.add("landing_vref", "V<sub>_Ref</sub>:")
+        self.add("landing_vref_tooltip",
+                 "A leszállási sebesség csomóban.")
+        
+        self.add("flighttype_scheduled", "menetrendszerinti")
+        self.add("flighttype_ot", "old-timer")
+        self.add("flighttype_vip", "VIP")
+        self.add("flighttype_charter", "charter")
+
+        self.add("finish_title", "Lezárás")
+        self.add("finish_help",
+                 "Lent olvasható némi statisztika a járat teljesítéséről.\n\n" \
+                 "Ellenőrízd az adatokat, az előző oldalakon is, és ha\n" \
+                 "megfelelnek, elmentheted vagy elküldheted a PIREP-et.")
+        self.add("finish_rating", "Pontszám:")
+        self.add("finish_flight_time", "Repülési idő:")
+        self.add("finish_block_time", "Blokk idő:")
+        self.add("finish_distance", "Repült táv:")
+        self.add("finish_fuel", "Elhasznált üzemanyag:")
+        self.add("finish_type", "_Típus:")
+        self.add("finish_type_tooltip", "Válaszd ki a repülés típusát.")
+        self.add("finish_online", "_Online repülés")
+        self.add("finish_online_tooltip",
+                 "Jelöld be, ha a repülésed a hálózaton történt, egyébként " \
+                 "szűntesd meg a kijelölést.")
+        self.add("finish_gate", "_Érkezési kapu:")
+        self.add("finish_gate_tooltip",
+                 "Válaszd ki azt a kaput vagy állóhelyet, ahová érkeztél LHBP-n.")
+        self.add("finish_save", "PIREP _mentése...")
+        self.add("finish_save_tooltip",
+                 "Kattints ide, hogy elmenthesd a PIREP-et egy fájlba a számítógépeden. " \
+                 "A PIREP-et később be lehet tölteni és el lehet küldeni.")
+        self.add("finish_save_title", "PIREP mentése")
+        self.add("finish_save_done", "A PIREP mentése sikerült")
+        self.add("finish_save_failed", "A PIREP mentése nem sikerült")
+        self.add("finish_save_failed_sec", "A részleteket lásd a debug naplóban.")
+
+        # M A 
+
+        self.add("info_comments", "_Megjegyzések")
+        self.add("info_defects", "Hib_ajelenségek")
+        self.add("info_delay", "Késés kódok")
+
+        # B V H Y R G F E P Z
+                 
+        self.add("info_delay_loading", "_Betöltési problémák")
+        self.add("info_delay_vatsim", "_VATSIM probléma")
+        self.add("info_delay_net", "_Hálózati problémák")
+        self.add("info_delay_atc", "Irán_yító hibája")
+        self.add("info_delay_system", "_Rendszer elszállás/fagyás")
+        self.add("info_delay_nav", "Navi_gációs probléma")
+        self.add("info_delay_traffic", "_Forgalmi problémák")
+        self.add("info_delay_apron", "_Előtér navigációs probléma")
+        self.add("info_delay_weather", "Időjárási _problémák")
+        self.add("info_delay_personal", "S_zemélyes okok")
+                 
+        self.add("statusbar_conn_tooltip",
+                 'A kapcsolat állapota.\n'
+                 '<span foreground="grey">Szürke</span>: nincs kapcsolat.\n'
+                 '<span foreground="red">Piros</span>: kapcsolódás folyamatban.\n'
+                 '<span foreground="green">Zöld</span>: a kapcsolat él.')
+        self.add("statusbar_stage_tooltip", "A repülés fázisa")
+        self.add("statusbar_time_tooltip", "A szimulátor ideje UTC-ben")
+        self.add("statusbar_rating_tooltip", "A repülés pontszáma")
+        self.add("statusbar_busy_tooltip", "A háttérfolyamatok állapota.")
+
+        self.add("flight_stage_boarding", u"beszállás")
+        self.add("flight_stage_pushback and taxi", u"hátratolás és kigurulás")
+        self.add("flight_stage_takeoff", u"felszállás")
+        self.add("flight_stage_RTO", u"megszakított felszállás")
+        self.add("flight_stage_climb", u"emelkedés")
+        self.add("flight_stage_cruise", u"utazó")
+        self.add("flight_stage_descent", u"süllyedés")
+        self.add("flight_stage_landing", u"leszállás")
+        self.add("flight_stage_taxi", u"begurulás")
+        self.add("flight_stage_parking", u"parkolás")
+        self.add("flight_stage_go-around", u"átstartolás")
+        self.add("flight_stage_end", u"kész")
+
+        self.add("statusicon_showmain", "Mutasd a főablakot")
+        self.add("statusicon_showmonitor", "Mutasd a monitor ablakot")
+        self.add("statusicon_quit", "Kilépés")
+        self.add("statusicon_stage", u"Fázis")
+        self.add("statusicon_rating", u"Pontszám")
+
+        self.add("update_title", "Frissítés")
+        self.add("update_needsudo",
+                 "Lenne mit frissíteni, de a program hozzáférési jogok\n"
+                 "hiányában nem tud írni a saját könyvtárába.\n\n"
+                 "Kattints az OK gombra, ha el szeretnél indítani egy\n"
+                 "segédprogramot adminisztrátori jogokkal, amely\n"
+                 "befejezné a frissítést, egyébként a Mégse gombra.")
+        self.add("update_manifest_progress", "A manifesztum letöltése...")
+        self.add("update_manifest_done", "A manifesztum letöltve...")
+        self.add("update_files_progress", "Fájlok letöltése...")
+        self.add("update_files_bytes", "%d bájtot töltöttem le %d bájtból")
+        self.add("update_renaming", "A letöltött fájlok átnevezése...")
+        self.add("update_renamed", "Átneveztem a(z) %s fájlt")
+        self.add("update_removing", "Fájlok törlése...")
+        self.add("update_removed", "Letöröltem a(z) %s fájlt")
+        self.add("update_writing_manifest", "Az új manifesztum írása")
+        self.add("update_finished",
+                 "A frissítés sikerült. Kattints az OK-ra a program újraindításához.")
+        self.add("update_nothing", "Nem volt mit frissíteni")
+        self.add("update_failed", "Nem sikerült, a részleteket lásd a debug naplóban.")
+
+        self.add("weighthelp_usinghelp", "_Használom a segítséget")
+        self.add("weighthelp_usinghelp_tooltip",
+                 "Ha bejelölöd, az alábbiakban kapsz egy kis segítséget "
+                 "a járathoz szükséges hasznos teher megállapításához. "
+                 "Ha igénybe veszed ezt a szolgáltatást, ez a tény "
+                 "a naplóba bekerül.")
+        self.add("weighthelp_header_calculated", "Elvárt/\nszámított")
+        self.add("weighthelp_header_simulator", "Szimulátor\nadatok")
+        self.add("weighthelp_header_simulator_tooltip",
+                 "Kattints erre a gombra a súlyadatoknak a szimulátortól "
+                 "való lekérdezéséhez. Az értékek lent jelennek meg. Ha "
+                 "egy érték a tűrés 10%-án belül van, akkor az "
+                 '<b><span foreground="darkgreen">zöld</span></b> '
+                 "színnel jelenik meg. Ha nem fér bele a tűrésbe, akkor "
+                 '<b><span foreground="red">piros</span></b>, '
+                 "egyébként "
+                 '<b><span foreground="orange">sárga</span></b> '
+                 "színben olvasható.")
+        self.add("weighthelp_crew", "Legénység (%s):")
+        self.add("weighthelp_pax", "Utasok (%s):")
+        self.add("weighthelp_baggage", "Poggyász:")
+        self.add("weighthelp_cargo", "Teher:")
+        self.add("weighthelp_mail", "Posta:")
+        self.add("weighthelp_payload", "Hasznos teher:")
+        self.add("weighthelp_dow", "DOW:")
+        self.add("weighthelp_zfw", "ZFW:")
+        self.add("weighthelp_gross", "Teljes tömeg:")
+        self.add("weighthelp_mzfw", "MZFW:")
+        self.add("weighthelp_mtow", "MTOW:")
+        self.add("weighthelp_mlw", "MLW:")
+        self.add("weighthelp_busy", "A tömegadatok lekérdezése...")
+
+        self.add("gates_fleet_title", "_Flotta")
+        self.add("gates_gates_title", "LHBP kapuk")
+        self.add("gates_tailno", "Lajstromjel")
+        self.add("gates_planestatus", "Állapot")
+        self.add("gates_refresh", "_Adatok frissítése")
+        self.add("gates_refresh_tooltip",
+                 "Kattints erre a gombra a fenti adatok frissítéséhez")
+        self.add("gates_planes_tooltip",
+                 "Ez a táblázat tartalmazza a MAVA flottája összes "
+                 "repülőgépének lajstromjelét és utolsó ismert helyét. "
+                 "Ha egy repülőgép olyan kapun áll, amelyet másik gép is "
+                 "elfoglal, akkor annak a repülőgépnek az adatai "
+                 "<b><span foreground=\"red\">piros</span></b> "
+                 "színnel jelennek meg.")
+        self.add("gates_gates_tooltip",
+                 "A MAVA repülőgépei által elfoglalt kapuk száma "
+                 '<b><span foreground="orange">sárga</span></b> színnel,'
+                 "a többié feketén jelenik meg.")
+        self.add("gates_plane_away", "TÁVOL")
+        self.add("gates_plane_parking", "PARKOL")
+        self.add("gates_plane_unknown", "ISMERETLEN")
+                 
+        self.add("prefs_title", "Beállítások")
+        self.add("prefs_tab_general", "_Általános")
+        self.add("prefs_tab_general_tooltip", "Általános beállítások")
+        self.add("prefs_tab_messages", "_Üzenetek")
+        self.add("prefs_tab_message_tooltip",
+                 "A szimulátorba és/vagy hangjelzés általi üzenetküldés be- "
+                 "és kikapcsolása")
+        self.add("prefs_tab_advanced", "H_aladó")
+        self.add("prefs_tab_advanced_tooltip",
+                 "Haladó beállítások: óvatosan módosítsd őket!")
+        self.add("prefs_language", "_Nyelv:")
+        self.add("prefs_language_tooltip",
+                 "A program által használt nyelv")
+        self.add("prefs_restart",
+                 "Újraindítás szükséges")
+        self.add("prefs_language_restart_sec",
+                 "A program nyelvének megváltoztatása csak egy újraindítást "
+                 "követően jut érvényre.")
+        self.add("prefs_lang_$system", "alapértelmezett")
+        self.add("prefs_lang_en_GB", "angol")
+        self.add("prefs_lang_hu_HU", "magyar")
+        self.add("prefs_hideMinimizedWindow",
+                 "A főablak _eltüntetése minimalizáláskor")
+        self.add("prefs_hideMinimizedWindow_tooltip",
+                 "Ha ezt kijelölöd, a főablak teljesen eltűnik, "
+                 "ha minimalizálod. A státuszikonra kattintással vagy annak "
+                 "menüje segítségével újra meg tudod jeleníteni.")
+        self.add("prefs_onlineGateSystem",
+                 "Az Online _Gate System használata")
+        self.add("prefs_onlineGateSystem_tooltip",
+                 "Ha ezt bejelölöd, a logger lekérdezi és frissíti az "
+                 "LHBP Online Gate System adatait.")
+        self.add("prefs_onlineACARS",
+                 "Az Online ACA_RS rendszer használata")
+        self.add("prefs_onlineACARS_tooltip",
+                 "Ha ezt bejölöd, a logger folyamatosan közli a repülésed "
+                 "adatait a MAVA Online ACARS rendszerrel.")
+        self.add("prefs_flaretimeFromFS",
+                 "A ki_lebegtetés idejét vedd a szimulátorból")
+        self.add("prefs_flaretimeFromFS_tooltip",
+                 "Ha ezt bejelölöd, a kilebegtetés idejét a szimulátor "
+                 "által visszaadott időbélyegek alapján számolja a program.")
+        self.add("prefs_syncFSTime",
+                 "_Szinkronizáld a szimulátor idéjét a számítógépével")
+        self.add("prefs_syncFSTime_tooltip",
+                 "Ha ez bejelölöd, a szimulátor belső óráját a program "
+                 "szinkronban tartja a számítógép órájával.")
+        self.add("prefs_pirepDirectory",
+                 "_PIREP-ek könyvtára:")
+        self.add("prefs_pirepDirectory_tooltip",
+                 "Az itt megadott könyvtárt ajánlja majd fel a program "
+                 "a PIREP-ek mentésekor.")
+        self.add("prefs_pirepDirectory_browser_title",
+                 "Válaszd ki a PIREP-ek könyvtárát")
+        self.add("prefs_update_auto",
+                 "Frissítsd a programot _automatikusan")
+        self.add("prefs_update_auto_tooltip",
+                 "Ha ez be van jelölve, a program induláskor frissítést "
+                 "keres, és ha talál, azokat letölti és telepíti. Ez "
+                 "biztosítja, hogy az elküldött PIREP minden megfelel "
+                 "a legújabb elvárásoknak.")
+        self.add("prefs_update_auto_warning",
+                 "Az automatikus frissítés kikapcsolása azt okozhatja, "
+                 "hogy a program Nálad lévő verziója elavulttá válik, "
+                 "és a PIREP-jeidet így nem fogadják el.")
+        self.add("prefs_update_url", "Frissítés _URL-je:")
+        self.add("prefs_update_url_tooltip",
+                 "Az URL, ahol a frissítéseket keresi a program. Csak akkor "
+                 "változtasd meg, ha biztos vagy a dolgodban!")
+
+        # A Á H M O Ü
+
+        self.add("prefs_msgs_fs", "Szimulátorban\nmegjelenítés")
+        self.add("prefs_msgs_sound", "Hangjelzés")
+        self.add("prefs_msgs_type_loggerError", "_Logger hibaüzenetek")
+        self.add("prefs_msgs_type_information",
+                 "_Információs üzenetek\n(pl. a repülés fázisa)")
+        self.add("prefs_msgs_type_fault",
+                 "Hi_baüzenetek\n(pl. a villogó fény hiba)")
+        self.add("prefs_msgs_type_nogo",
+                 "_NOGO hibaüzenetek\n(pl. MTOW NOGO)")
+        self.add("prefs_msgs_type_gateSystem",
+                 "_Kapukezelő rendszer üzenetei\n(pl. a szabad kapuk listája)")
+        self.add("prefs_msgs_type_environment",
+                 "Kö_rnyezeti üzenetek\n(pl. \"welcome to XY aiport\")")
+        self.add("prefs_msgs_type_help",
+                 "_Segítő üzenetek\n(pl. \"don't forget to set VREF\")")
+        self.add("prefs_msgs_type_visibility",
+                 "Lá_tótávolság üzenetek")
+
+        self.add("loadPIREP_browser_title", "Válaszd ki a betöltendő PIREP-et")
+        self.add("loadPIREP_filter_pireps", "PIREP fájlok")
+        self.add("loadPIREP_filter_all", "Összes fájl")
+        self.add("loadPIREP_failed", "Nem tudtam betölteni a PIREP-et")
+        self.add("loadPIREP_failed_sec",
+                 "A részleteket lásd a debug naplóban.")
+        self.add("loadPIREP_send_title", "PIREP")
+        self.add("loadPIREP_send_help",
+                 "A betöltött PIREP főbb adatai:")
+        self.add("loadPIREP_send_flightno", "Járatszám:")
+        self.add("loadPIREP_send_date", "Dátum:")
+        self.add("loadPIREP_send_from", "Honnan:")
+        self.add("loadPIREP_send_to", "Hová:")
+        self.add("loadPIREP_send_rating", "Pontszám:")
+
+        self.add("sendPIREP", "PIREP _elküldése...")
+        self.add("sendPIREP_tooltip",
+                 "Kattints ide, hogy elküldd a PIREP-et a MAVA szerverére javításra.")
+        self.add("sendPIREP_busy", "PIREP küldése...")
+        self.add("sendPIREP_success",
+                 "A PIREP elküldése sikeres volt.")
+        self.add("sendPIREP_success_sec",
+                 "Várhatod félelmet nem ismerő PIREP javítóink alapos észrevételeit! :)")
+        self.add("sendPIREP_already",
+                 "Ehhez a járathoz már küldtél be PIREP-et!")
+        self.add("sendPIREP_already_sec",
+                 "A korábban beküldött PIREP-et törölheted a MAVA honlapján.")
+        self.add("sendPIREP_notavail",
+                 "Ez a járat már nem elérhető!")
+        self.add("sendPIREP_unknown",
+                 "A MAVA szervere ismeretlen hibaüzenettel tért vissza.")
+        self.add("sendPIREP_unknown_sec",
+                 "A debug naplóban részletesebb információt találsz.")
+        self.add("sendPIREP_failed",
+                 "Nem tudtam elküldeni a PIREP-et a MAVA szerverére.")
+        self.add("sendPIREP_failed_sec",
+                 "Lehet, hogy hálózati probléma áll fenn, amely esetben később\n" \
+                 "újra próbálkozhatsz. Lehet azonban hiba is a loggerben:\n" \
+                 "részletesebb információt találhatsz a debug naplóban.")
+
+#------------------------------------------------------------------------------
+
+# The fallback language is English
+_English()
+
+# We also support Hungarian
+_Hungarian()
+
+#------------------------------------------------------------------------------
Index: src/mlx/i18n.py
===================================================================
--- src/mlx/i18n.py	(revision 155)
+++ 	(revision )
@@ -1,1283 +1,0 @@
-# Internationalization support
-# -*- coding: utf-8 -*-
-
-#------------------------------------------------------------------------------
-
-import locale
-
-#------------------------------------------------------------------------------
-
-_strings = None
-_fallback = None
-
-#------------------------------------------------------------------------------
-
-def setLanguage(language):
-    """Setup the internationalization support for the given language."""
-    print "i18n.setLanguage", language
-    _Strings.set(language)
-    
-#------------------------------------------------------------------------------
-
-def xstr(key):
-    """Get the string for the given key in the current language.
-
-    If not found, the fallback language is searched. If that is not found
-    either, the key itself is returned within curly braces."""
-    s = _Strings.current()[key]
-    if s is None:
-        s = _Strings.fallback()[key]
-    return "{" + key + "}" if s is None else s
-    
-#------------------------------------------------------------------------------
-
-class _Strings(object):
-    """A collection of strings belonging to a certain language."""
-    # The registry of the string collections. This is a mapping from
-    # language codes to the actual collection objects
-    _instances = {}
-
-    # The fallback instance
-    _fallback = None
-
-    # The currently used instance.
-    _current = None
-
-    @staticmethod
-    def _find(language):
-        """Find an instance for the given language.
-
-        First the language is searched for as is. If not found, it is truncated
-        from the end until the last underscore and that is searched for. And so
-        on, until no underscore remains.
-
-        If nothing is found this way, the 
-        """
-        while language:
-            if language in _Strings._instances:
-                return _Strings._instances[language]
-            underscoreIndex = language.rfind("_")
-            language = language[:underscoreIndex] if underscoreIndex>0 else ""
-        return _Strings._fallback
-
-    @staticmethod
-    def set(language):
-        """Set the string for the given language.
-
-        A String instance is searched for the given language (see
-        _Strings._find()). Otherwise the fallback language is used."""
-        strings = _Strings._find(language)
-        assert strings is not None
-        if _Strings._current is not None and \
-           _Strings._current is not _Strings._fallback:
-            _Strings._current.finalize()
-        if strings is not _Strings._fallback:
-            strings.initialize()
-        _Strings._current = strings
-
-    @staticmethod
-    def fallback():
-        """Get the fallback."""
-        return _Strings._fallback
-
-    @staticmethod
-    def current():
-        """Get the current."""
-        return _Strings._current
-
-    def __init__(self, languages, fallback = False):
-        """Construct an empty strings object."""
-        self._strings = {}
-        for language in languages:
-            _Strings._instances[language] = self
-        if fallback:
-            _Strings._fallback = self
-            self.initialize()
-
-    def initialize(self):
-        """Initialize the strings.
-
-        This should be overridden in children to setup the strings."""
-        pass
-
-    def finalize(self):
-        """Finalize the object.
-
-        This releases the string dictionary to free space."""
-        self._strings = {}
-
-    def add(self, id, s):
-        """Add the given text as the string with the given ID.
-
-        If the ID is already in the object, that is an assertion failure!"""
-        assert id not in self._strings
-        self._strings[id] = s
-
-    def __getitem__(self, key):
-        """Get the string  with the given key."""
-        return self._strings[key] if key in self._strings else None
-
-#------------------------------------------------------------------------------
-
-class _English(_Strings):
-    """The strings for the English language."""
-    def __init__(self):
-        """Construct the object."""
-        super(_English, self).__init__(["en_GB", "en"], fallback = True)
-
-    def initialize(self):
-        """Initialize the strings."""
-        self.add("button_ok", "_OK")
-        self.add("button_cancel", "_Cancel")
-        self.add("button_yes", "_Yes")
-        self.add("button_no", "_No")
-        self.add("button_browse", "Browse...")
-        
-        self.add("menu_file", "File")
-        self.add("menu_file_loadPIREP", "_Load PIREP...")
-        self.add("menu_file_loadPIREP_key", "l")
-        self.add("menu_file_quit", "_Quit")
-        self.add("menu_file_quit_key", "q")
-        self.add("quit_question", "Are you sure to quit the logger?")
-
-        self.add("menu_tools", "Tools")
-        self.add("menu_tools_prefs", "_Preferences")
-        self.add("menu_tools_prefs_key", "p")
-
-        self.add("menu_view", "View")
-        self.add("menu_view_monitor", "Show _monitor window")
-        self.add("menu_view_monitor_key", "m")
-        self.add("menu_view_debug", "Show _debug log")
-        self.add("menu_view_debug_key", "d")
-        
-        self.add("tab_flight", "_Flight")
-        self.add("tab_flight_tooltip", "Flight wizard")
-        self.add("tab_flight_info", "Flight _info")
-        self.add("tab_flight_info_tooltip", "Further information regarding the flight")
-        self.add("tab_weight_help", "_Help")
-        self.add("tab_weight_help_tooltip", "Help to calculate the weights")
-        self.add("tab_log", "_Log")
-        self.add("tab_log_tooltip",
-                 "The log of your flight that will be sent to the MAVA website")
-        self.add("tab_gates", "_Gates")        
-        self.add("tab_gates_tooltip", "The status of the MAVA fleet and the gates at LHBP")        
-        self.add("tab_debug_log", "_Debug log")
-        self.add("tab_debug_log_tooltip", "Log with debugging information.")
-
-        self.add("conn_failed", "Cannot connect to the simulator.")
-        self.add("conn_failed_sec",
-                 "Rectify the situation, and press <b>Try again</b> "
-                 "to try the connection again, " 
-                 "or <b>Cancel</b> to cancel the flight.")
-        self.add("conn_broken",
-                 "The connection to the simulator failed unexpectedly.")
-        self.add("conn_broken_sec",
-                 "If the simulator has crashed, restart it "
-                 "and restore your flight as much as possible "
-                 "to the state it was in before the crash. "
-                 "Then press <b>Reconnect</b> to reconnect.\n\n"
-                 "If you want to cancel the flight, press <b>Cancel</b>.")
-        self.add("button_tryagain", "_Try again")
-        self.add("button_reconnect", "_Reconnect")
-
-        self.add("login", "Login")
-        self.add("loginHelp",
-                 "Enter your MAVA pilot's ID and password to\n" \
-                 "log in to the MAVA website and download\n" \
-                 "your booked flights.")
-        self.add("label_pilotID", "Pil_ot ID:")
-        self.add("login_pilotID_tooltip",
-                 "Enter your MAVA pilot's ID. This usually starts with a "
-                 "'P' followed by 3 digits.")
-        self.add("label_password", "_Password:")
-        self.add("login_password_tooltip",
-                 "Enter the password for your pilot's ID")
-        self.add("remember_password", "_Remember password")
-        self.add("login_remember_tooltip",
-                 "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.")
-        self.add("button_login", "Logi_n")
-        self.add("login_button_tooltip", "Click to log in.")
-        self.add("login_busy", "Logging in...")
-        self.add("login_invalid", "Invalid pilot's ID or password.")
-        self.add("login_invalid_sec",
-                 "Check the ID and try to reenter the password.")
-        self.add("login_failconn",
-                 "Failed to connect to the MAVA website.")
-        self.add("login_failconn_sec", "Try again in a few minutes.")
-
-        self.add("button_next", "_Next")
-        self.add("button_next_tooltip", "Click to go to the next page.")
-        self.add("button_previous", "_Previous")
-        self.add("button_previous_tooltip", "Click to go to the previous page.")
-
-        self.add("flightsel_title", "Flight selection")
-        self.add("flightsel_help", "Select the flight you want to perform.")
-        self.add("flightsel_chelp", "You have selected the flight highlighted below.")
-        self.add("flightsel_no", "Flight no.")
-        self.add("flightsel_deptime", "Departure time [UTC]")
-        self.add("flightsel_from", "From")
-        self.add("flightsel_to", "To")
-
-        self.add("fleet_busy", "Retrieving fleet...")
-        self.add("fleet_failed",
-                 "Failed to retrieve the information on the fleet.")
-        self.add("fleet_update_busy", "Updating plane status...")
-        self.add("fleet_update_failed",
-                 "Failed to update the status of the airplane.")
-        
-        self.add("gatesel_title", "LHBP gate selection")
-        self.add("gatesel_help",
-                 "The airplane's gate position is invalid.\n\n" \
-                 "Select the gate from which you\n" \
-                 "would like to begin the flight.")
-        self.add("gatesel_conflict", "Gate conflict detected again.")
-        self.add("gatesel_conflict_sec",
-                 "Try to select a different gate.")
-
-        self.add("connect_title", "Connect to the simulator")
-        self.add("connect_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.")
-        self.add("connect_chelp",
-                 "The basic data of your flight can be read below.")
-        self.add("connect_flightno", "Flight number:")
-        self.add("connect_acft", "Aircraft:")
-        self.add("connect_tailno", "Tail number:")
-        self.add("connect_airport", "Airport:")
-        self.add("connect_gate", "Gate:")
-        self.add("button_connect", "_Connect")
-        self.add("button_connect_tooltip", "Click to connect to the simulator.")
-        self.add("connect_busy", "Connecting to the simulator...")
-
-        self.add("payload_title", "Payload")
-        self.add("payload_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.")
-        self.add("payload_chelp",
-                 "You can see the weights in the briefing\n" \
-                 "and the cargo weight you have selected below.\n\n" \
-                 "You can also query the ZFW reported by the simulator.")
-        self.add("payload_crew", "Crew:")
-        self.add("payload_pax", "Passengers:")
-        self.add("payload_bag", "Baggage:")
-        self.add("payload_cargo", "_Cargo:")
-        self.add("payload_cargo_tooltip",
-                 "The weight of the cargo for your flight.")
-        self.add("payload_mail", "Mail:")
-        self.add("payload_zfw", "Calculated ZFW:")
-        self.add("payload_fszfw", "_ZFW from FS:")
-        self.add("payload_fszfw_tooltip",
-                 "Click here to refresh the ZFW value from the simulator.")
-        self.add("payload_zfw_busy", "Querying ZFW...")
-
-        self.add("time_title", "Time")
-        self.add("time_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.")
-        self.add("time_chelp",
-                 "The departure and arrival times are displayed below in UTC.\n\n" \
-                 "You can also query the current UTC time from the simulator.\n")
-        self.add("time_departure", "Departure:")
-        self.add("time_arrival", "Arrival:")
-        self.add("time_fs", "_Time from FS:")
-        self.add("time_fs_tooltip",
-                 "Click here to query the current UTC time from the simulator.")
-        self.add("time_busy", "Querying time...")
-
-        self.add("fuel_title", "Fuel")
-        self.add("fuel_help",
-                 "Enter the amount of fuel in kilograms that need to be "
-                 "present in each tank below.\n\n"
-                 "When you press <b>Next</b>, the necessary amount of fuel\n"
-                 "will be pumped into or out of the tanks.")
-        self.add("fuel_chelp",
-                 "The amount of fuel tanked into your aircraft at the\n"
-                 "beginning of the flight can be seen below.")
-
-        self.add("fuel_tank_centre", "_Centre\n")
-        self.add("fuel_tank_left", "L_eft\n")
-        self.add("fuel_tank_right", "_Right\n")
-        self.add("fuel_tank_left_aux", "Left\nA_ux")
-        self.add("fuel_tank_right_aux", "Right\nAu_x")
-        self.add("fuel_tank_left_tip", "Left\n_Tip")
-        self.add("fuel_tank_right_tip", "Right\nTip")
-        self.add("fuel_tank_external1", "External\n_1")
-        self.add("fuel_tank_external2", "External\n_2")
-        self.add("fuel_tank_centre2", "Ce_ntre\n2")
-        self.add("fuel_get_busy", "Querying fuel information...")
-        self.add("fuel_pump_busy", "Pumping fuel...")
-        self.add("fuel_tank_tooltip",
-                 "This part displays the current level of the fuel in the "
-                 "compared to its capacity. The "
-                 '<span color="turquoise">turquoise</span> '
-                 "slider shows the level that should be loaded into the tank "
-                 "for the flight. You can click anywhere in the widget to "
-                 "move the slider there. Or you can grab it by holding down "
-                 "the left button of your mouse, and move the pointer up or "
-                 "down. The scroll wheel on your mouse also increments or "
-                 "decrements the amount of fuel by 10. If you hold down "
-                 "the <b>Shift</b> key while scrolling, the steps will be "
-                 "100, or with the <b>Control</b> key, 1.")
-
-        self.add("route_title", "Route")
-        self.add("route_help",
-                 "Set your cruise flight level below, and\n" \
-                 "if necessary, edit the flight plan.")
-        self.add("route_chelp",
-                 "If necessary, you can modify the cruise level and\n" \
-                 "the flight plan below during flight.\n" \
-                 "If so, please, add a comment on why " \
-                 "the modification became necessary.")
-        self.add("route_level", "_Cruise level:")
-        self.add("route_level_tooltip",
-                 "The cruise flight level. Click on the arrows to increment "
-                 "or decrement by 10, or enter the number on the keyboard.")
-        self.add("route_route", "_Route")
-        self.add("route_route_tooltip",
-                 "The planned flight route in the standard format.")
-        self.add("route_down_notams", "Downloading NOTAMs...")
-        self.add("route_down_metars", "Downloading METARs...")
-
-        self.add("briefing_title", "Briefing (%d/2): %s")
-        self.add("briefing_departure", "departure")
-        self.add("briefing_arrival", "arrival")
-        self.add("briefing_help",
-                 "Read carefully the NOTAMs and METAR below.\n\n" \
-                 "You can edit the METAR if your simulator or network\n" \
-                 "provides different weather.")
-        self.add("briefing_chelp",
-                 "If your simulator or network provides a different\n" \
-                 "weather, you can edit the METAR below.")
-        self.add("briefing_notams_init", "LHBP NOTAMs")
-        self.add("briefing_metar_init", "LHBP METAR")
-        self.add("briefing_button",
-                 "I have read the briefing and am _ready to fly!")
-        self.add("briefing_notams_template", "%s NOTAMs")
-        self.add("briefing_metar_template", "%s _METAR")
-        self.add("briefing_notams_failed", "Could not download NOTAMs")
-        self.add("briefing_notams_missing",
-                 "Could not download NOTAM for this airport")
-        self.add("briefing_metar_failed", "Could not download METAR")
-
-        self.add("takeoff_title", "Takeoff")
-        self.add("takeoff_help",
-                 "Enter the runway and SID used, as well as the speeds.")
-        self.add("takeoff_chelp",
-                 "The runway, SID and takeoff speeds logged can be seen below.")
-        self.add("takeoff_runway", "Run_way:")
-        self.add("takeoff_runway_tooltip",
-                 "The runway the takeoff is performed from.")
-        self.add("takeoff_sid", "_SID:")
-        self.add("takeoff_sid_tooltip",
-                 "The name of the Standard Instrument Deparature procedure followed.")
-        self.add("takeoff_v1", "V<sub>_1</sub>:")
-        self.add("takeoff_v1_tooltip", "The takeoff decision speed in knots.")
-        self.add("label_knots", "knots")
-        self.add("takeoff_vr", "V<sub>_R</sub>:")
-        self.add("takeoff_vr_tooltip", "The takeoff rotation speed in knots.")
-        self.add("takeoff_v2", "V<sub>_2</sub>:")
-        self.add("takeoff_v2_tooltip", "The takeoff safety speed in knots.")
-
-        self.add("landing_title", "Landing")
-        self.add("landing_help",
-                 "Enter the STAR and/or transition, runway,\n" \
-                 "approach type and V<sub>Ref</sub> used.")
-        self.add("landing_chelp",
-                 "The STAR and/or transition, runway, approach\n" \
-                 "type and V<sub>Ref</sub> logged can be seen below.")
-        self.add("landing_star", "_STAR:")
-        self.add("landing_star_tooltip",
-                 "The name of Standard Terminal Arrival Route followed.")
-        self.add("landing_transition", "_Transition:")
-        self.add("landing_transition_tooltip",
-                 "The name of transition executed or VECTORS if vectored by ATC.")
-        self.add("landing_runway", "Run_way:")
-        self.add("landing_runway_tooltip",
-                 "The runway the landing is performed on.")
-        self.add("landing_approach", "_Approach type:")
-        self.add("landing_approach_tooltip",
-                 "The type of the approach, e.g. ILS or VISUAL.")
-        self.add("landing_vref", "V<sub>_Ref</sub>:")
-        self.add("landing_vref_tooltip",
-                 "The landing reference speed in knots.")
-
-        self.add("flighttype_scheduled", "scheduled")
-        self.add("flighttype_ot", "old-timer")
-        self.add("flighttype_vip", "VIP")
-        self.add("flighttype_charter", "charter")
-
-        self.add("finish_title", "Finish")
-        self.add("finish_help",
-                 "There are some statistics about your flight below.\n\n" \
-                 "Review the data, also on earlier pages, and if you are\n" \
-                 "satisfied, you can save or send your PIREP.")
-        self.add("finish_rating", "Flight rating:")
-        self.add("finish_flight_time", "Flight time:")
-        self.add("finish_block_time", "Block time:")
-        self.add("finish_distance", "Distance flown:")
-        self.add("finish_fuel", "Fuel used:")
-        self.add("finish_type", "_Type:")
-        self.add("finish_type_tooltip", "Select the type of the flight.")
-        self.add("finish_online", "_Online flight")
-        self.add("finish_online_tooltip",
-                 "Check if your flight was online, uncheck otherwise.")
-        self.add("finish_gate", "_Arrival gate:")
-        self.add("finish_gate_tooltip",
-                 "Select the gate or stand at which you have arrived to LHBP.")
-        self.add("finish_save", "Sa_ve PIREP...")
-        self.add("finish_save_tooltip",
-                 "Click to save the PIREP into a file on your computer. " \
-                 "The PIREP can be loaded and sent later.")
-        self.add("finish_save_title", "Save PIREP into")
-        self.add("finish_save_done", "The PIREP was saved successfully")
-        self.add("finish_save_failed", "Failed to save the PIREP")
-        self.add("finish_save_failed_sec", "See the debug log for the details.")
-
-        # C D
-        
-        self.add("info_comments", "_Comments")
-        self.add("info_defects", "Flight _defects")
-        self.add("info_delay", "Delay codes")
-
-        # O V N E Y T R A W P
-        
-        self.add("info_delay_loading", "L_oading problems")
-        self.add("info_delay_vatsim", "_VATSIM problem")
-        self.add("info_delay_net", "_Net problems")
-        self.add("info_delay_atc", "Controll_er's fault")
-        self.add("info_delay_system", "S_ystem crash/freeze")
-        self.add("info_delay_nav", "Naviga_tion problem")
-        self.add("info_delay_traffic", "T_raffic problems")
-        self.add("info_delay_apron", "_Apron navigation problem")
-        self.add("info_delay_weather", "_Weather problems")
-        self.add("info_delay_personal", "_Personal reasons")
-
-        self.add("statusbar_conn_tooltip",
-                 '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.')
-        self.add("statusbar_stage_tooltip", "The flight stage")
-        self.add("statusbar_time_tooltip", "The simulator time in UTC")
-        self.add("statusbar_rating_tooltip", "The flight rating")
-        self.add("statusbar_busy_tooltip", "The status of the background tasks.")
-
-        self.add("flight_stage_boarding", "boarding")
-        self.add("flight_stage_pushback and taxi", "pushback and taxi")
-        self.add("flight_stage_takeoff", "takeoff")
-        self.add("flight_stage_RTO", "RTO")
-        self.add("flight_stage_climb", "climb")
-        self.add("flight_stage_cruise", "cruise")
-        self.add("flight_stage_descent", "descent")
-        self.add("flight_stage_landing", "landing")
-        self.add("flight_stage_taxi", "taxi")
-        self.add("flight_stage_parking", "parking")
-        self.add("flight_stage_go-around", "go-around")
-        self.add("flight_stage_end", "end")
-
-        self.add("statusicon_showmain", "Show main window")
-        self.add("statusicon_showmonitor", "Show monitor window")
-        self.add("statusicon_quit", "Quit")
-        self.add("statusicon_stage", "Stage")
-        self.add("statusicon_rating", "Rating")
-
-        self.add("update_title", "Update")
-        self.add("update_needsudo",
-                 "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.")
-        self.add("update_manifest_progress", "Downloading manifest...")
-        self.add("update_manifest_done", "Downloaded manifest...")
-        self.add("update_files_progress", "Downloading files...")
-        self.add("update_files_bytes", "Downloaded %d of %d bytes")
-        self.add("update_renaming", "Renaming downloaded files...")
-        self.add("update_renamed", "Renamed %s")
-        self.add("update_removing", "Removing files...")
-        self.add("update_removed", "Removed %s")
-        self.add("update_writing_manifest", "Writing the new manifest")
-        self.add("update_finished",
-                 "Finished updating. Press OK to restart the program.")
-        self.add("update_nothing", "There was nothing to update")
-        self.add("update_failed", "Failed, see the debug log for details.")
-
-        self.add("weighthelp_usinghelp", "_Using help")
-        self.add("weighthelp_usinghelp_tooltip",
-                 "If you check this, some help will be displayed on how "
-                 "to calculate the payload weight for your flight. "
-                 "Note, that the usage of this facility will be logged.")
-        self.add("weighthelp_header_calculated", "Requested/\ncalculated")
-        self.add("weighthelp_header_simulator", "Simulator\ndata")
-        self.add("weighthelp_header_simulator_tooltip",
-                 "Click this button to retrieve the weight values from the "
-                 "simulator, which will be displayed below. If a value is "
-                 "within 10% of the tolerance, it is displayed in "
-                 '<b><span foreground="darkgreen">green</span></b>, '
-                 "if it is out of the tolerance, it is displayed in "
-                 '<b><span foreground="red">red</span></b>, '
-                 "otherwise in"
-                 '<b><span foreground="orange">yellow</span></b>.')
-        self.add("weighthelp_crew", "Crew (%s):")
-        self.add("weighthelp_pax", "Passengers (%s):")
-        self.add("weighthelp_baggage", "Baggage:")
-        self.add("weighthelp_cargo", "Cargo:")
-        self.add("weighthelp_mail", "Mail:")
-        self.add("weighthelp_payload", "Payload:")
-        self.add("weighthelp_dow", "DOW:")
-        self.add("weighthelp_zfw", "ZFW:")
-        self.add("weighthelp_gross", "Gross weight:")
-        self.add("weighthelp_mzfw", "MZFW:")
-        self.add("weighthelp_mtow", "MTOW:")
-        self.add("weighthelp_mlw", "MLW:")
-        self.add("weighthelp_busy", "Querying weight data...")
-
-        self.add("gates_fleet_title", "Fl_eet")
-        self.add("gates_gates_title", "LHBP gates")
-        self.add("gates_tailno", "Tail nr.")
-        self.add("gates_planestatus", "Status")
-        self.add("gates_refresh", "_Refresh data")
-        self.add("gates_refresh_tooltip",
-                 "Click this button to refresh the status data above")
-        self.add("gates_planes_tooltip",
-                 "This table lists all the planes in the MAVA fleet and their "
-                 "last known location. If a plane is conflicting with another "
-                 "because of occupying the same gate its data is displayed in "
-                 "<b><span foreground=\"red\">red</span></b>.")
-        self.add("gates_gates_tooltip",
-                 "The numbers of the gates occupied by MAVA planes are "
-                 "displayed in "
-                 '<b><span foreground="orange">yellow</span></b>, '
-                 "while available gates in black.")
-        self.add("gates_plane_away", "AWAY")
-        self.add("gates_plane_parking", "PARKING")
-        self.add("gates_plane_unknown", "UNKNOWN")
-
-        self.add("prefs_title", "Preferences")
-        self.add("prefs_tab_general", "_General")
-        self.add("prefs_tab_general_tooltip", "General preferences")
-        self.add("prefs_tab_messages", "_Messages")
-        self.add("prefs_tab_message_tooltip",
-                 "Enable/disable message notifications in FS and/or by sound")
-        self.add("prefs_tab_advanced", "_Advanced")
-        self.add("prefs_tab_advanced_tooltip",
-                 "Advanced preferences, edit with care!")
-        self.add("prefs_language", "_Language:")
-        self.add("prefs_language_tooltip",
-                 "The language of the program")
-        self.add("prefs_restart",
-                 "Restart needed")
-        self.add("prefs_language_restart_sec",
-                 "If you change the language, the program should be restarted "
-                 "so that the change has an effect.")
-        self.add("prefs_lang_$system", "system default")
-        self.add("prefs_lang_en_GB", "English")
-        self.add("prefs_lang_hu_HU", "Hungarian")
-        self.add("prefs_hideMinimizedWindow",
-                 "_Hide the main window when minimized")
-        self.add("prefs_hideMinimizedWindow_tooltip",
-                 "If checked, the main window will be hidden completely "
-                 "when minimized. You can still make it appear by "
-                 "clicking on the status icon or using its popup menu.")
-        self.add("prefs_onlineGateSystem",
-                 "_Use the Online Gate System")
-        self.add("prefs_onlineGateSystem_tooltip",
-                 "If this is checked, the logger will query and update the "
-                 "LHBP Online Gate System.")
-        self.add("prefs_onlineACARS",
-                 "Use the Online ACA_RS System")
-        self.add("prefs_onlineACARS_tooltip",
-                 "If this is checked, the logger will continuously update "
-                 "the MAVA Online ACARS System with your flight's data.")
-        self.add("prefs_flaretimeFromFS",
-                 "Take flare _time from the simulator")
-        self.add("prefs_flaretimeFromFS_tooltip",
-                 "If this is checked, the time of the flare will be calculated "
-                 "from timestamps returned by the simulator.")
-        self.add("prefs_syncFSTime",
-                 "_Synchronize the time in FS with the computer's clock")
-        self.add("prefs_syncFSTime_tooltip",
-                 "If this is checked the flight simulator's internal clock "
-                 "will always be synchronized to the computer's clock.")
-        self.add("prefs_pirepDirectory",
-                 "_PIREP directory:")
-        self.add("prefs_pirepDirectory_tooltip",
-                 "The directory that will be offered by default when "
-                 "saving a PIREP.")
-        self.add("prefs_pirepDirectory_browser_title",
-                 "Select PIREP directory")
-        self.add("prefs_update_auto", "Update the program auto_matically")
-        self.add("prefs_update_auto_tooltip",
-                 "If checked the program will look for updates when "
-                 "it is starting, and if new updates are found, they "
-                 "will be downloaded and installed. This ensures that "
-                 "the PIREP you send will always conform to the latest "
-                 "expectations of the airline.")
-        self.add("prefs_update_auto_warning",
-                 "Disabling automatic updates may result in "
-                 "your version of the program becoming out of date "
-                 "and your PIREPs not being accepted.")
-        self.add("prefs_update_url", "Update _URL:")
-        self.add("prefs_update_url_tooltip",
-                 "The URL from which to download the updates. Change this "
-                 "only if you know what you are doing!")
-
-        # A C G M O S
-        
-        self.add("prefs_msgs_fs", "Displayed in FS")
-        self.add("prefs_msgs_sound", "Sound alert")
-        self.add("prefs_msgs_type_loggerError", "Logger _Error Messages")
-        self.add("prefs_msgs_type_information",
-                 "_Information Messages\n(e.g. flight status)")
-        self.add("prefs_msgs_type_fault",
-                 "_Fault Messages\n(e.g. strobe light fault)")
-        self.add("prefs_msgs_type_nogo",
-                 "_NOGO Fault messages\n(e.g. MTOW NOGO)")
-        self.add("prefs_msgs_type_gateSystem",
-                 "Ga_te System Messages\n(e.g. available gates)")
-        self.add("prefs_msgs_type_environment",
-                 "Envi_ronment Messages\n(e.g. \"welcome to XY aiport\")")
-        self.add("prefs_msgs_type_help",
-                 "_Help Messages\n(e.g. \"don't forget to set VREF\")")
-        self.add("prefs_msgs_type_visibility",
-                 "_Visibility Messages")
-
-        self.add("loadPIREP_browser_title", "Select the PIREP to load")
-        self.add("loadPIREP_filter_pireps", "PIREP files")
-        self.add("loadPIREP_filter_all", "All files")
-        self.add("loadPIREP_failed", "Failed to load the PIREP")
-        self.add("loadPIREP_failed_sec", "See the debug log for the details.")
-        self.add("loadPIREP_send_title", "PIREP")
-        self.add("loadPIREP_send_help",
-                 "The main data of the PIREP loaded:")
-        self.add("loadPIREP_send_flightno", "Flight number:")
-        self.add("loadPIREP_send_date", "Date:")
-        self.add("loadPIREP_send_from", "From:")
-        self.add("loadPIREP_send_to", "To:")
-        self.add("loadPIREP_send_rating", "Rating:")
-        
-        self.add("sendPIREP", "_Send PIREP...")
-        self.add("sendPIREP_tooltip",
-                 "Click to send the PIREP to the MAVA website for further review.")
-        self.add("sendPIREP_busy", "Sending PIREP...")
-        self.add("sendPIREP_success",
-                 "The PIREP was sent successfully.")
-        self.add("sendPIREP_success_sec",
-                 "Await the thorough scrutiny by our fearless PIREP reviewers! :)")
-        self.add("sendPIREP_already",
-                 "The PIREP for this flight has already been sent!")
-        self.add("sendPIREP_already_sec",
-                 "You may clear the old PIREP on the MAVA website.")
-        self.add("sendPIREP_notavail",
-                 "This flight is not available anymore!")
-        self.add("sendPIREP_unknown",
-                 "The MAVA website returned with an unknown error.")
-        self.add("sendPIREP_unknown_sec",
-                 "See the debug log for more information.")
-        self.add("sendPIREP_failed",
-                 "Could not send the PIREP to the MAVA website.")
-        self.add("sendPIREP_failed_sec",
-                 "This can be a network problem, in which case\n" \
-                 "you may try again later. Or it can be a bug;\n" \
-                 "see the debug log for more information.")
-
-#------------------------------------------------------------------------------
-
-class _Hungarian(_Strings):
-    """The strings for the Hungarian language."""
-    def __init__(self):
-        """Construct the object."""
-        super(_Hungarian, self).__init__(["hu_HU", "hu"])
-
-    def initialize(self):
-        """Initialize the strings."""
-        self.add("button_ok", "_OK")
-        self.add("button_cancel", "_Mégse")
-        self.add("button_yes", "_Igen")
-        self.add("button_no", "_Nem")
-        self.add("button_browse", "Keresés...")
-        
-        self.add("menu_file", "Fájl")
-        self.add("menu_file_loadPIREP", "PIREP be_töltése...")
-        self.add("menu_file_loadPIREP_key", "t")
-        self.add("menu_file_quit", "_Kilépés")
-        self.add("menu_file_quit_key", "k")
-        self.add("quit_question", "Biztosan ki akarsz lépni?")
-
-        self.add("menu_tools", "Eszközök")
-        self.add("menu_tools_prefs", "_Beállítások")
-        self.add("menu_tools_prefs_key", "b")
-
-        self.add("menu_view", "Nézet")
-        self.add("menu_view_monitor", "Mutasd a _monitor ablakot")
-        self.add("menu_view_monitor_key", "m")
-        self.add("menu_view_debug", "Mutasd a _debug naplót")
-        self.add("menu_view_debug_key", "d")
-        
-        self.add("tab_flight", "_Járat")
-        self.add("tab_flight_tooltip", "Járat varázsló")
-        self.add("tab_flight_info", "Járat _info")
-        self.add("tab_flight_info_tooltip", "Egyéb információk a járat teljesítésével kapcsolatban")
-        self.add("tab_weight_help", "_Segítség")
-        self.add("tab_weight_help_tooltip", "Segítség a súlyszámításhoz")
-        self.add("tab_log", "_Napló")
-        self.add("tab_log_tooltip",
-                 "A járat naplója, amit majd el lehet küldeni a MAVA szerverére")
-        self.add("tab_gates", "_Kapuk")        
-        self.add("tab_gates_tooltip", "A MAVA flotta és LHBP kapuinak állapota")        
-        self.add("tab_debug_log", "_Debug napló")
-        self.add("tab_debug_log_tooltip",
-                 "Hibakereséshez használható információkat tartalmazó napló.")
-
-        self.add("conn_failed", "Nem tudtam kapcsolódni a szimulátorhoz.")
-        self.add("conn_failed_sec",
-                 "Korrigáld a problémát, majd nyomd meg az "
-                 "<b>Próbáld újra</b> gombot a újrakapcsolódáshoz, "
-                 "vagy a <b>Mégse</b> gombot a járat megszakításához.")
-        self.add("conn_broken",
-                 "A szimulátorral való kapcsolat váratlanul megszakadt.")
-        self.add("conn_broken_sec",
-                 "Ha a szimulátor elszállt, indítsd újra "
-                 "és állítsd vissza a repülésed elszállás előtti "
-                 "állapotát amennyire csak lehet. "
-                 "Ezután nyomd meg az <b>Újrakapcsolódás</b> gombot "
-                 "a kapcsolat ismételt felvételéhez.\n\n"
-                 "Ha meg akarod szakítani a repülést, nyomd meg a "
-                 "<b>Mégse</b> gombot.")
-        self.add("button_tryagain", "_Próbáld újra")
-        self.add("button_reconnect", "Újra_kapcsolódás")
-
-        self.add("login", "Bejelentkezés")
-        self.add("loginHelp",
-                 "Írd be a MAVA pilóta azonosítódat és a\n" \
-                 "bejelentkezéshez használt jelszavadat,\n" \
-                 "hogy választhass a foglalt járataid közül.")
-        self.add("label_pilotID", "_Azonosító:")
-        self.add("login_pilotID_tooltip",
-                 "Írd be a MAVA pilóta azonosítódat. Ez általában egy 'P' "
-                 "betűvel kezdődik, melyet 3 számjegy követ.")
-        self.add("label_password", "Je_lszó:")
-        self.add("login_password_tooltip",
-                 "Írd be a pilóta azonosítódhoz tartozó jelszavadat.")
-        self.add("remember_password", "_Emlékezz a jelszóra")
-        self.add("login_remember_tooltip",
-                 "Ha ezt kiválasztod, a jelszavadat eltárolja a program, így "
-                 "nem kell mindig újból beírnod. Vedd azonban figyelembe, "
-                 "hogy a jelszót szövegként tároljuk, így bárki elolvashatja, "
-                 "aki hozzáfér a fájljaidhoz.")
-        self.add("button_login", "_Bejelentkezés")
-        self.add("login_button_tooltip", "Kattints ide a bejelentkezéshez.")
-        self.add("login_busy", "Bejelentkezés...")
-        self.add("login_invalid", "Érvénytelen azonosító vagy jelszó.")
-        self.add("login_invalid_sec",
-                 "Ellenőrízd az azonosítót, és próbáld meg újra beírni a jelszót.")
-        self.add("login_failconn",
-                 "Nem sikerült kapcsolódni a MAVA honlaphoz.")
-        self.add("login_failconn_sec", "Próbáld meg pár perc múlva.")
-        
-        self.add("button_next", "_Előre")
-        self.add("button_next_tooltip",
-                 "Kattints ide, hogy a következő lapra ugorj.")
-        self.add("button_previous", "_Vissza")
-        self.add("button_previous_tooltip",
-                 "Kattints ide, hogy az előző lapra ugorj.")
-
-        self.add("flightsel_title", "Járatválasztás")
-        self.add("flightsel_help", "Válaszd ki a járatot, amelyet le szeretnél repülni.")
-        self.add("flightsel_chelp", "A lent kiemelt járatot választottad.")
-        self.add("flightsel_no", "Járatszám")
-        self.add("flightsel_deptime", "Indulás ideje [UTC]")
-        self.add("flightsel_from", "Honnan")
-        self.add("flightsel_to", "Hová")
-
-        self.add("fleet_busy", "Flottaadatok letöltése...")
-        self.add("fleet_failed",
-                 "Nem sikerült letöltenem a flotta adatait.")
-        self.add("fleet_update_busy", "Repülőgép pozíció frissítése...")
-        self.add("fleet_update_failed",
-                 "Nem sikerült frissítenem a repülőgép pozícióját.")
-
-        self.add("gatesel_title", "LHBP kapuválasztás")
-        self.add("gatesel_help",
-                 "A repülőgép kapu pozíciója érvénytelen.\n\n" \
-                 "Válaszd ki azt a kaput, ahonnan\n" \
-                 "el szeretnéd kezdeni a járatot.")
-        self.add("gatesel_conflict", "Ismét kapuütközés történt.")
-        self.add("gatesel_conflict_sec",
-                 "Próbálj egy másik kaput választani.")
-
-        self.add("connect_title", "Kapcsolódás a szimulátorhoz")
-        self.add("connect_help",
-                 "Tölsd be a lent látható repülőgépet a szimulátorba\n" \
-                 "az alább megadott reptérre és kapuhoz.\n\n" \
-                 "Ezután nyomd meg a Kapcsolódás gombot a kapcsolódáshoz.")
-        self.add("connect_chelp",
-                 "A járat alapadatai lent olvashatók.")
-        self.add("connect_flightno", "Járatszám:")
-        self.add("connect_acft", "Típus:")
-        self.add("connect_tailno", "Lajstromjel:")
-        self.add("connect_airport", "Repülőtér:")
-        self.add("connect_gate", "Kapu:")
-        self.add("button_connect", "K_apcsolódás")
-        self.add("button_connect_tooltip",
-                 "Kattints ide a szimulátorhoz való kapcsolódáshoz.")
-        self.add("connect_busy", "Kapcsolódás a szimulátorhoz...")
-
-        self.add("payload_title", "Terhelés")
-        self.add("payload_help",
-                 "Az eligazítás az alábbi tömegeket tartalmazza.\n" \
-                 "Allítsd be a teherszállítmány tömegét itt, a hasznos "
-                 "terhet pedig a szimulátorban.\n\n" \
-                 "Azt is ellenőrízheted, hogy a szimulátor milyen ZFW-t jelent.")
-        self.add("payload_chelp",
-                 "Lent láthatók az eligazításban szereplő tömegek, valamint\n" \
-                 "a teherszállítmány általad megadott tömege.\n\n" \
-                 "Azt is ellenőrízheted, hogy a szimulátor milyen ZFW-t jelent.")
-        self.add("payload_crew", "Legénység:")
-        self.add("payload_pax", "Utasok:")
-        self.add("payload_bag", "Poggyász:")
-        self.add("payload_cargo", "_Teher:")
-        self.add("payload_cargo_tooltip",
-                 "A teherszállítmány tömege.")
-        self.add("payload_mail", "Posta:")
-        self.add("payload_zfw", "Kiszámolt ZFW:")
-        self.add("payload_fszfw", "_ZFW a szimulátorból:")
-        self.add("payload_fszfw_tooltip",
-                 "Kattints ide, hogy frissítsd a ZFW értékét a szimulátorból.")
-        self.add("payload_zfw_busy", "ZFW lekérdezése...")
-        
-        self.add("time_title", "Menetrend")
-        self.add("time_help",
-                 "Az indulás és az érkezés ideje lent látható UTC szerint.\n\n" \
-                 "A szimulátor aktuális UTC szerinti idejét is lekérdezheted.\n" \
-                 "Győzödj meg arról, hogy elég időd van a repülés előkészítéséhez.")
-        self.add("time_chelp",
-                 "Az indulás és az érkezés ideje lent látható UTC szerint.\n\n" \
-                 "A szimulátor aktuális UTC szerinti idejét is lekérdezheted.")
-        self.add("time_departure", "Indulás:")
-        self.add("time_arrival", "Érkezés:")
-        self.add("time_fs", "Idő a s_zimulátorból:")
-        self.add("time_fs_tooltip",
-                 "Kattings ide, hogy frissítsd a szimulátor aktuális UTC szerint idejét.")
-        self.add("time_busy", "Idő lekérdezése...")
-
-        self.add("fuel_title", "Üzemanyag")
-        self.add("fuel_help",
-                 "Írd be az egyes tartályokba szükséges üzemanyag "
-                 "mennyiségét kilogrammban.\n\n"
-                 "Ha megnyomod az <b>Előre</b> gombot, a megadott mennyiségű\n"
-                 "üzemanyag bekerül a tartályokba.")
-        self.add("fuel_chelp",
-                 "A repülés elején az egyes tartályokba tankolt\n"
-                 "üzemanyag mennyisége lent látható.")
-
-        # A B D E I G J K N O P S T V Y Z
-        
-        self.add("fuel_tank_centre", "Kö_zépső\n")
-        self.add("fuel_tank_left", "_Bal\n")
-        self.add("fuel_tank_right", "J_obb\n")
-        self.add("fuel_tank_left_aux", "Bal\nkie_gészítő")
-        self.add("fuel_tank_right_aux", "Jobb\nkiegészí_tő")
-        self.add("fuel_tank_left_tip", "B_al\nszárnyvég")
-        self.add("fuel_tank_right_tip", "Jobb\nszárn_yvég")
-        self.add("fuel_tank_external1", "Külső\n_1")
-        self.add("fuel_tank_external2", "Külső\n_2")
-        self.add("fuel_tank_centre2", "Közé_pső\n2")
-        self.add("fuel_get_busy", "Az üzemanyag lekérdezése...")
-        self.add("fuel_pump_busy", "Az üzemanyag pumpálása...")
-        self.add("fuel_tank_tooltip",
-                 "Ez mutatja az üzemanyag szintjét a tartályban annak "
-                 "kapacitásához mérve. A "
-                 '<span color="turquoise">türkizkék</span> '
-                 "csúszka mutatja a repüléshez kívánt szintet. "
-                 "Ha a bal gombbal bárhová kattintasz az ábrán, a csúszka "
-                 "odaugrik. Ha a gombot lenyomva tartod, és az egérmutatót "
-                 "föl-le mozgatod, a csúszka követi azt. Az egered görgőjével "
-                 "is kezelheted a csúszkát. Alaphelyzetben az üzemanyag "
-                 "mennyisége 10-zel nő, illetve csökken a görgetés irányától "
-                 "függően. Ha a <b>Shift</b> billentyűt lenyomva tartod, "
-                 "növekmény 100, a <b>Control</b> billentyűvel pedig 1 lesz.")
-
-        self.add("route_title", "Útvonal")
-        self.add("route_help",
-                 "Állítsd be az utazószintet lent, és ha szükséges,\n" \
-                 "módosítsd az útvonaltervet.")
-        self.add("route_chelp",
-                 "Ha szükséges, lent módosíthatod az utazószintet és\n" \
-                 "az útvonaltervet repülés közben is.\n" \
-                 "Ha így teszel, légy szíves a megjegyzés mezőben " \
-                 "ismertesd ennek okát.")
-        self.add("route_level", "_Utazószint:")
-        self.add("route_level_tooltip", "Az utazószint.")
-        self.add("route_route", "Út_vonal")
-        self.add("route_route_tooltip", "Az útvonal a szokásos formátumban.")
-        self.add("route_down_notams", "NOTAM-ok letöltése...")
-        self.add("route_down_metars", "METAR-ok letöltése...")
-        
-        self.add("briefing_title", "Eligazítás (%d/2): %s")
-        self.add("briefing_departure", "indulás")
-        self.add("briefing_arrival", "érkezés")
-        self.add("briefing_help",
-                 "Olvasd el figyelmesen a lenti NOTAM-okat és METAR-t.\n\n" \
-                 "Ha a szimulátor vagy hálózat más időjárást ad,\n" \
-                 "a METAR-t módosíthatod.")
-        self.add("briefing_chelp",
-                 "Ha a szimulátor vagy hálózat más időjárást ad,\n" \
-                 "a METAR-t módosíthatod.")
-        self.add("briefing_notams_init", "LHBP NOTAM-ok")
-        self.add("briefing_metar_init", "LHBP METAR")
-        self.add("briefing_button",
-                 "Elolvastam az eligazítást, és készen állok a _repülésre!")
-        self.add("briefing_notams_template", "%s NOTAM-ok")
-        self.add("briefing_metar_template", "%s _METAR")
-        self.add("briefing_notams_failed", "Nem tudtam letölteni a NOTAM-okat.")
-        self.add("briefing_notams_missing",
-                 "Ehhez a repülőtérhez nem találtam NOTAM-ot.")
-        self.add("briefing_metar_failed", "Nem tudtam letölteni a METAR-t.")
-        
-        self.add("takeoff_title", "Felszállás")
-        self.add("takeoff_help",
-                 "Írd be a felszállásra használt futópálya és SID nevét, valamint a sebességeket.")
-        self.add("takeoff_chelp",
-                 "A naplózott futópálya, SID és a sebességek lent olvashatók.")
-        self.add("takeoff_runway", "_Futópálya:")
-        self.add("takeoff_runway_tooltip",
-                 "A felszállásra használt futópálya.")
-        self.add("takeoff_sid", "_SID:")
-        self.add("takeoff_sid_tooltip",
-                 "Az alkalmazott szabványos műszeres indulási eljárás neve.")
-        self.add("takeoff_v1", "V<sub>_1</sub>:")
-        self.add("takeoff_v1_tooltip", "Az elhatározási sebesség csomóban.")
-        self.add("label_knots", "csomó")
-        self.add("takeoff_vr", "V<sub>_R</sub>:")
-        self.add("takeoff_vr_tooltip", "Az elemelkedési sebesség csomóban.")
-        self.add("takeoff_v2", "V<sub>_2</sub>:")
-        self.add("takeoff_v2_tooltip", "A biztonságos emelkedési sebesség csomóban.")
-        
-        self.add("landing_title", "Leszállás")
-        self.add("landing_help",
-                 "Írd be az alkalmazott STAR és/vagy bevezetési eljárás nevét,\n"
-                 "a használt futópályát, a megközelítés módját, és a V<sub>Ref</sub>-et.")
-        self.add("landing_chelp",
-                 "Az alkalmazott STAR és/vagy bevezetési eljárás neve, a használt\n"
-                 "futópálya, a megközelítés módja és a V<sub>Ref</sub> lent olvasható.")
-        self.add("landing_star", "_STAR:")
-        self.add("landing_star_tooltip",
-                 "A követett szabványos érkezési eljárás neve.")
-        self.add("landing_transition", "_Bevezetés:")
-        self.add("landing_transition_tooltip",
-                 "Az alkalmazott bevezetési eljárás neve, vagy VECTORS, "
-                 "ha az irányítás vezetett be.")
-        self.add("landing_runway", "_Futópálya:")
-        self.add("landing_runway_tooltip",
-                 "A leszállásra használt futópálya.")
-        self.add("landing_approach", "_Megközelítés típusa:")
-        self.add("landing_approach_tooltip",
-                 "A megközelítgés típusa, pl. ILS vagy VISUAL.")
-        self.add("landing_vref", "V<sub>_Ref</sub>:")
-        self.add("landing_vref_tooltip",
-                 "A leszállási sebesség csomóban.")
-        
-        self.add("flighttype_scheduled", "menetrendszerinti")
-        self.add("flighttype_ot", "old-timer")
-        self.add("flighttype_vip", "VIP")
-        self.add("flighttype_charter", "charter")
-
-        self.add("finish_title", "Lezárás")
-        self.add("finish_help",
-                 "Lent olvasható némi statisztika a járat teljesítéséről.\n\n" \
-                 "Ellenőrízd az adatokat, az előző oldalakon is, és ha\n" \
-                 "megfelelnek, elmentheted vagy elküldheted a PIREP-et.")
-        self.add("finish_rating", "Pontszám:")
-        self.add("finish_flight_time", "Repülési idő:")
-        self.add("finish_block_time", "Blokk idő:")
-        self.add("finish_distance", "Repült táv:")
-        self.add("finish_fuel", "Elhasznált üzemanyag:")
-        self.add("finish_type", "_Típus:")
-        self.add("finish_type_tooltip", "Válaszd ki a repülés típusát.")
-        self.add("finish_online", "_Online repülés")
-        self.add("finish_online_tooltip",
-                 "Jelöld be, ha a repülésed a hálózaton történt, egyébként " \
-                 "szűntesd meg a kijelölést.")
-        self.add("finish_gate", "_Érkezési kapu:")
-        self.add("finish_gate_tooltip",
-                 "Válaszd ki azt a kaput vagy állóhelyet, ahová érkeztél LHBP-n.")
-        self.add("finish_save", "PIREP _mentése...")
-        self.add("finish_save_tooltip",
-                 "Kattints ide, hogy elmenthesd a PIREP-et egy fájlba a számítógépeden. " \
-                 "A PIREP-et később be lehet tölteni és el lehet küldeni.")
-        self.add("finish_save_title", "PIREP mentése")
-        self.add("finish_save_done", "A PIREP mentése sikerült")
-        self.add("finish_save_failed", "A PIREP mentése nem sikerült")
-        self.add("finish_save_failed_sec", "A részleteket lásd a debug naplóban.")
-
-        # M A 
-
-        self.add("info_comments", "_Megjegyzések")
-        self.add("info_defects", "Hib_ajelenségek")
-        self.add("info_delay", "Késés kódok")
-
-        # B V H Y R G F E P Z
-                 
-        self.add("info_delay_loading", "_Betöltési problémák")
-        self.add("info_delay_vatsim", "_VATSIM probléma")
-        self.add("info_delay_net", "_Hálózati problémák")
-        self.add("info_delay_atc", "Irán_yító hibája")
-        self.add("info_delay_system", "_Rendszer elszállás/fagyás")
-        self.add("info_delay_nav", "Navi_gációs probléma")
-        self.add("info_delay_traffic", "_Forgalmi problémák")
-        self.add("info_delay_apron", "_Előtér navigációs probléma")
-        self.add("info_delay_weather", "Időjárási _problémák")
-        self.add("info_delay_personal", "S_zemélyes okok")
-                 
-        self.add("statusbar_conn_tooltip",
-                 'A kapcsolat állapota.\n'
-                 '<span foreground="grey">Szürke</span>: nincs kapcsolat.\n'
-                 '<span foreground="red">Piros</span>: kapcsolódás folyamatban.\n'
-                 '<span foreground="green">Zöld</span>: a kapcsolat él.')
-        self.add("statusbar_stage_tooltip", "A repülés fázisa")
-        self.add("statusbar_time_tooltip", "A szimulátor ideje UTC-ben")
-        self.add("statusbar_rating_tooltip", "A repülés pontszáma")
-        self.add("statusbar_busy_tooltip", "A háttérfolyamatok állapota.")
-
-        self.add("flight_stage_boarding", u"beszállás")
-        self.add("flight_stage_pushback and taxi", u"hátratolás és kigurulás")
-        self.add("flight_stage_takeoff", u"felszállás")
-        self.add("flight_stage_RTO", u"megszakított felszállás")
-        self.add("flight_stage_climb", u"emelkedés")
-        self.add("flight_stage_cruise", u"utazó")
-        self.add("flight_stage_descent", u"süllyedés")
-        self.add("flight_stage_landing", u"leszállás")
-        self.add("flight_stage_taxi", u"begurulás")
-        self.add("flight_stage_parking", u"parkolás")
-        self.add("flight_stage_go-around", u"átstartolás")
-        self.add("flight_stage_end", u"kész")
-
-        self.add("statusicon_showmain", "Mutasd a főablakot")
-        self.add("statusicon_showmonitor", "Mutasd a monitor ablakot")
-        self.add("statusicon_quit", "Kilépés")
-        self.add("statusicon_stage", u"Fázis")
-        self.add("statusicon_rating", u"Pontszám")
-
-        self.add("update_title", "Frissítés")
-        self.add("update_needsudo",
-                 "Lenne mit frissíteni, de a program hozzáférési jogok\n"
-                 "hiányában nem tud írni a saját könyvtárába.\n\n"
-                 "Kattints az OK gombra, ha el szeretnél indítani egy\n"
-                 "segédprogramot adminisztrátori jogokkal, amely\n"
-                 "befejezné a frissítést, egyébként a Mégse gombra.")
-        self.add("update_manifest_progress", "A manifesztum letöltése...")
-        self.add("update_manifest_done", "A manifesztum letöltve...")
-        self.add("update_files_progress", "Fájlok letöltése...")
-        self.add("update_files_bytes", "%d bájtot töltöttem le %d bájtból")
-        self.add("update_renaming", "A letöltött fájlok átnevezése...")
-        self.add("update_renamed", "Átneveztem a(z) %s fájlt")
-        self.add("update_removing", "Fájlok törlése...")
-        self.add("update_removed", "Letöröltem a(z) %s fájlt")
-        self.add("update_writing_manifest", "Az új manifesztum írása")
-        self.add("update_finished",
-                 "A frissítés sikerült. Kattints az OK-ra a program újraindításához.")
-        self.add("update_nothing", "Nem volt mit frissíteni")
-        self.add("update_failed", "Nem sikerült, a részleteket lásd a debug naplóban.")
-
-        self.add("weighthelp_usinghelp", "_Használom a segítséget")
-        self.add("weighthelp_usinghelp_tooltip",
-                 "Ha bejelölöd, az alábbiakban kapsz egy kis segítséget "
-                 "a járathoz szükséges hasznos teher megállapításához. "
-                 "Ha igénybe veszed ezt a szolgáltatást, ez a tény "
-                 "a naplóba bekerül.")
-        self.add("weighthelp_header_calculated", "Elvárt/\nszámított")
-        self.add("weighthelp_header_simulator", "Szimulátor\nadatok")
-        self.add("weighthelp_header_simulator_tooltip",
-                 "Kattints erre a gombra a súlyadatoknak a szimulátortól "
-                 "való lekérdezéséhez. Az értékek lent jelennek meg. Ha "
-                 "egy érték a tűrés 10%-án belül van, akkor az "
-                 '<b><span foreground="darkgreen">zöld</span></b> '
-                 "színnel jelenik meg. Ha nem fér bele a tűrésbe, akkor "
-                 '<b><span foreground="red">piros</span></b>, '
-                 "egyébként "
-                 '<b><span foreground="orange">sárga</span></b> '
-                 "színben olvasható.")
-        self.add("weighthelp_crew", "Legénység (%s):")
-        self.add("weighthelp_pax", "Utasok (%s):")
-        self.add("weighthelp_baggage", "Poggyász:")
-        self.add("weighthelp_cargo", "Teher:")
-        self.add("weighthelp_mail", "Posta:")
-        self.add("weighthelp_payload", "Hasznos teher:")
-        self.add("weighthelp_dow", "DOW:")
-        self.add("weighthelp_zfw", "ZFW:")
-        self.add("weighthelp_gross", "Teljes tömeg:")
-        self.add("weighthelp_mzfw", "MZFW:")
-        self.add("weighthelp_mtow", "MTOW:")
-        self.add("weighthelp_mlw", "MLW:")
-        self.add("weighthelp_busy", "A tömegadatok lekérdezése...")
-
-        self.add("gates_fleet_title", "_Flotta")
-        self.add("gates_gates_title", "LHBP kapuk")
-        self.add("gates_tailno", "Lajstromjel")
-        self.add("gates_planestatus", "Állapot")
-        self.add("gates_refresh", "_Adatok frissítése")
-        self.add("gates_refresh_tooltip",
-                 "Kattints erre a gombra a fenti adatok frissítéséhez")
-        self.add("gates_planes_tooltip",
-                 "Ez a táblázat tartalmazza a MAVA flottája összes "
-                 "repülőgépének lajstromjelét és utolsó ismert helyét. "
-                 "Ha egy repülőgép olyan kapun áll, amelyet másik gép is "
-                 "elfoglal, akkor annak a repülőgépnek az adatai "
-                 "<b><span foreground=\"red\">piros</span></b> "
-                 "színnel jelennek meg.")
-        self.add("gates_gates_tooltip",
-                 "A MAVA repülőgépei által elfoglalt kapuk száma "
-                 '<b><span foreground="orange">sárga</span></b> színnel,'
-                 "a többié feketén jelenik meg.")
-        self.add("gates_plane_away", "TÁVOL")
-        self.add("gates_plane_parking", "PARKOL")
-        self.add("gates_plane_unknown", "ISMERETLEN")
-                 
-        self.add("prefs_title", "Beállítások")
-        self.add("prefs_tab_general", "_Általános")
-        self.add("prefs_tab_general_tooltip", "Általános beállítások")
-        self.add("prefs_tab_messages", "_Üzenetek")
-        self.add("prefs_tab_message_tooltip",
-                 "A szimulátorba és/vagy hangjelzés általi üzenetküldés be- "
-                 "és kikapcsolása")
-        self.add("prefs_tab_advanced", "H_aladó")
-        self.add("prefs_tab_advanced_tooltip",
-                 "Haladó beállítások: óvatosan módosítsd őket!")
-        self.add("prefs_language", "_Nyelv:")
-        self.add("prefs_language_tooltip",
-                 "A program által használt nyelv")
-        self.add("prefs_restart",
-                 "Újraindítás szükséges")
-        self.add("prefs_language_restart_sec",
-                 "A program nyelvének megváltoztatása csak egy újraindítást "
-                 "követően jut érvényre.")
-        self.add("prefs_lang_$system", "alapértelmezett")
-        self.add("prefs_lang_en_GB", "angol")
-        self.add("prefs_lang_hu_HU", "magyar")
-        self.add("prefs_hideMinimizedWindow",
-                 "A főablak _eltüntetése minimalizáláskor")
-        self.add("prefs_hideMinimizedWindow_tooltip",
-                 "Ha ezt kijelölöd, a főablak teljesen eltűnik, "
-                 "ha minimalizálod. A státuszikonra kattintással vagy annak "
-                 "menüje segítségével újra meg tudod jeleníteni.")
-        self.add("prefs_onlineGateSystem",
-                 "Az Online _Gate System használata")
-        self.add("prefs_onlineGateSystem_tooltip",
-                 "Ha ezt bejelölöd, a logger lekérdezi és frissíti az "
-                 "LHBP Online Gate System adatait.")
-        self.add("prefs_onlineACARS",
-                 "Az Online ACA_RS rendszer használata")
-        self.add("prefs_onlineACARS_tooltip",
-                 "Ha ezt bejölöd, a logger folyamatosan közli a repülésed "
-                 "adatait a MAVA Online ACARS rendszerrel.")
-        self.add("prefs_flaretimeFromFS",
-                 "A ki_lebegtetés idejét vedd a szimulátorból")
-        self.add("prefs_flaretimeFromFS_tooltip",
-                 "Ha ezt bejelölöd, a kilebegtetés idejét a szimulátor "
-                 "által visszaadott időbélyegek alapján számolja a program.")
-        self.add("prefs_syncFSTime",
-                 "_Szinkronizáld a szimulátor idéjét a számítógépével")
-        self.add("prefs_syncFSTime_tooltip",
-                 "Ha ez bejelölöd, a szimulátor belső óráját a program "
-                 "szinkronban tartja a számítógép órájával.")
-        self.add("prefs_pirepDirectory",
-                 "_PIREP-ek könyvtára:")
-        self.add("prefs_pirepDirectory_tooltip",
-                 "Az itt megadott könyvtárt ajánlja majd fel a program "
-                 "a PIREP-ek mentésekor.")
-        self.add("prefs_pirepDirectory_browser_title",
-                 "Válaszd ki a PIREP-ek könyvtárát")
-        self.add("prefs_update_auto",
-                 "Frissítsd a programot _automatikusan")
-        self.add("prefs_update_auto_tooltip",
-                 "Ha ez be van jelölve, a program induláskor frissítést "
-                 "keres, és ha talál, azokat letölti és telepíti. Ez "
-                 "biztosítja, hogy az elküldött PIREP minden megfelel "
-                 "a legújabb elvárásoknak.")
-        self.add("prefs_update_auto_warning",
-                 "Az automatikus frissítés kikapcsolása azt okozhatja, "
-                 "hogy a program Nálad lévő verziója elavulttá válik, "
-                 "és a PIREP-jeidet így nem fogadják el.")
-        self.add("prefs_update_url", "Frissítés _URL-je:")
-        self.add("prefs_update_url_tooltip",
-                 "Az URL, ahol a frissítéseket keresi a program. Csak akkor "
-                 "változtasd meg, ha biztos vagy a dolgodban!")
-
-        # A Á H M O Ü
-
-        self.add("prefs_msgs_fs", "Szimulátorban\nmegjelenítés")
-        self.add("prefs_msgs_sound", "Hangjelzés")
-        self.add("prefs_msgs_type_loggerError", "_Logger hibaüzenetek")
-        self.add("prefs_msgs_type_information",
-                 "_Információs üzenetek\n(pl. a repülés fázisa)")
-        self.add("prefs_msgs_type_fault",
-                 "Hi_baüzenetek\n(pl. a villogó fény hiba)")
-        self.add("prefs_msgs_type_nogo",
-                 "_NOGO hibaüzenetek\n(pl. MTOW NOGO)")
-        self.add("prefs_msgs_type_gateSystem",
-                 "_Kapukezelő rendszer üzenetei\n(pl. a szabad kapuk listája)")
-        self.add("prefs_msgs_type_environment",
-                 "Kö_rnyezeti üzenetek\n(pl. \"welcome to XY aiport\")")
-        self.add("prefs_msgs_type_help",
-                 "_Segítő üzenetek\n(pl. \"don't forget to set VREF\")")
-        self.add("prefs_msgs_type_visibility",
-                 "Lá_tótávolság üzenetek")
-
-        self.add("loadPIREP_browser_title", "Válaszd ki a betöltendő PIREP-et")
-        self.add("loadPIREP_filter_pireps", "PIREP fájlok")
-        self.add("loadPIREP_filter_all", "Összes fájl")
-        self.add("loadPIREP_failed", "Nem tudtam betölteni a PIREP-et")
-        self.add("loadPIREP_failed_sec",
-                 "A részleteket lásd a debug naplóban.")
-        self.add("loadPIREP_send_title", "PIREP")
-        self.add("loadPIREP_send_help",
-                 "A betöltött PIREP főbb adatai:")
-        self.add("loadPIREP_send_flightno", "Járatszám:")
-        self.add("loadPIREP_send_date", "Dátum:")
-        self.add("loadPIREP_send_from", "Honnan:")
-        self.add("loadPIREP_send_to", "Hová:")
-        self.add("loadPIREP_send_rating", "Pontszám:")
-
-        self.add("sendPIREP", "PIREP _elküldése...")
-        self.add("sendPIREP_tooltip",
-                 "Kattints ide, hogy elküldd a PIREP-et a MAVA szerverére javításra.")
-        self.add("sendPIREP_busy", "PIREP küldése...")
-        self.add("sendPIREP_success",
-                 "A PIREP elküldése sikeres volt.")
-        self.add("sendPIREP_success_sec",
-                 "Várhatod félelmet nem ismerő PIREP javítóink alapos észrevételeit! :)")
-        self.add("sendPIREP_already",
-                 "Ehhez a járathoz már küldtél be PIREP-et!")
-        self.add("sendPIREP_already_sec",
-                 "A korábban beküldött PIREP-et törölheted a MAVA honlapján.")
-        self.add("sendPIREP_notavail",
-                 "Ez a járat már nem elérhető!")
-        self.add("sendPIREP_unknown",
-                 "A MAVA szervere ismeretlen hibaüzenettel tért vissza.")
-        self.add("sendPIREP_unknown_sec",
-                 "A debug naplóban részletesebb információt találsz.")
-        self.add("sendPIREP_failed",
-                 "Nem tudtam elküldeni a PIREP-et a MAVA szerverére.")
-        self.add("sendPIREP_failed_sec",
-                 "Lehet, hogy hálózati probléma áll fenn, amely esetben később\n" \
-                 "újra próbálkozhatsz. Lehet azonban hiba is a loggerben:\n" \
-                 "részletesebb információt találhatsz a debug naplóban.")
-
-#------------------------------------------------------------------------------
-
-# The fallback language is English
-_English()
-
-# We also support Hungarian
-_Hungarian()
-
-#------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 160)
+++ src/mlx/logger.py	(revision 160)
@@ -0,0 +1,127 @@
+# Module for the logging.
+
+#--------------------------------------------------------------------------------------
+
+from fs import sendMessage
+import const
+import util
+
+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):
+        """Construct the logger."""
+        self._lines = []
+        self._faults = {}
+        self._faultLineIndexes = []
+        self._output = output
+
+    @property
+    def lines(self):
+        """Get the lines of the log."""
+        return self._lines
+
+    @property
+    def faultLineIndexes(self):
+        """Get the array of the indexes of the log line that contains a
+        fault."""
+        return self._faultLineIndexes
+
+    def reset(self):
+        """Reset the logger.
+
+        The faults logged so far will be cleared."""
+        self._lines = []
+        self._faults.clear()
+        self._faultLineIndexes = []
+                
+    def message(self, timestamp, msg):
+        """Put a simple textual message into the log with the given timestamp."""
+        timeStr = util.getTimestampString(timestamp)
+        return self._logLine(msg, timeStr)
+
+    def untimedMessage(self, msg):
+        """Put an untimed message into the log."""
+        return self._logLine(msg)
+
+    def debug(self, msg):
+        """Log a debug message."""
+        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(),))
+        else:
+            sendMessage(const.MESSAGETYPE_INFORMATION, "Flight stage: " + s, 3)
+        
+    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
+        text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
+               else "%s (%.1f)" % (what, score)
+        lineIndex = self.message(timestamp, text)
+        self._faultLineIndexes.append(lineIndex)
+        (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
+                                  if score==Logger.NO_GO_SCORE \
+                                  else (const.MESSAGETYPE_FAULT, 5)
+        sendMessage(messageType, text, duration)            
+
+    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
+
+    def updateLine(self, index, line):
+        """Update the line at the given index with the given string."""
+        (timeStr, _line) = self._lines[index]
+        self._lines[index] = (timeStr, line)
+        self._output.updateFlightLogLine(index, timeStr, line)
+
+    def _logLine(self, line, timeStr = None):
+        """Log the given line."""
+        index = len(self._lines)
+        self._lines.append((timeStr, line))
+        self._output.addFlightLogLine(timeStr, line)
+        return index
+        
+#--------------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 160)
+++ 	(revision )
@@ -1,127 +1,0 @@
-# Module for the logging.
-
-#--------------------------------------------------------------------------------------
-
-from fs import sendMessage
-import const
-import util
-
-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):
-        """Construct the logger."""
-        self._lines = []
-        self._faults = {}
-        self._faultLineIndexes = []
-        self._output = output
-
-    @property
-    def lines(self):
-        """Get the lines of the log."""
-        return self._lines
-
-    @property
-    def faultLineIndexes(self):
-        """Get the array of the indexes of the log line that contains a
-        fault."""
-        return self._faultLineIndexes
-
-    def reset(self):
-        """Reset the logger.
-
-        The faults logged so far will be cleared."""
-        self._lines = []
-        self._faults.clear()
-        self._faultLineIndexes = []
-                
-    def message(self, timestamp, msg):
-        """Put a simple textual message into the log with the given timestamp."""
-        timeStr = util.getTimestampString(timestamp)
-        return self._logLine(msg, timeStr)
-
-    def untimedMessage(self, msg):
-        """Put an untimed message into the log."""
-        return self._logLine(msg)
-
-    def debug(self, msg):
-        """Log a debug message."""
-        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(),))
-        else:
-            sendMessage(const.MESSAGETYPE_INFORMATION, "Flight stage: " + s, 3)
-        
-    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
-        text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
-               else "%s (%.1f)" % (what, score)
-        lineIndex = self.message(timestamp, text)
-        self._faultLineIndexes.append(lineIndex)
-        (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
-                                  if score==Logger.NO_GO_SCORE \
-                                  else (const.MESSAGETYPE_FAULT, 5)
-        sendMessage(messageType, text, duration)            
-
-    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
-
-    def updateLine(self, index, line):
-        """Update the line at the given index with the given string."""
-        (timeStr, _line) = self._lines[index]
-        self._lines[index] = (timeStr, line)
-        self._output.updateFlightLogLine(index, timeStr, line)
-
-    def _logLine(self, line, timeStr = None):
-        """Log the given line."""
-        index = len(self._lines)
-        self._lines.append((timeStr, line))
-        self._output.addFlightLogLine(timeStr, line)
-        return index
-        
-#--------------------------------------------------------------------------------------
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 160)
+++ src/mlx/mlx.py	(revision 160)
@@ -0,0 +1,81 @@
+# The main program
+
+from config import Config
+from i18n import setLanguage
+from sound import initializeSound
+
+import os
+import sys
+
+#--------------------------------------------------------------------------------------
+
+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 restart(args = []):
+    """Restart the program with the given arguments."""
+    print "Restarting with args", args
+    programPath = os.path.join(os.path.dirname(sys.argv[0]),
+                               "runmlx.exe" if os.name=="nt" else "runmlx.sh")
+    if os.name=="nt":
+        import win32api
+        try:
+            programPath = win32api.GetShortPathName(programPath)
+        except:
+            programPath = os.path.join(os.path.dirname(sys.argv[0]),
+                                       "runmlx.bat")
+            programPath = win32api.GetShortPathName(programPath)
+
+    args = [programPath] + args
+
+    os.execv(programPath, args)    
+
+#--------------------------------------------------------------------------------------
+
+def main():
+    """The main operation of the program."""
+    programDirectory = os.path.dirname(sys.argv[0])
+
+    config = Config()
+    config.load()
+
+    if (len(sys.argv)<=1 or sys.argv[1]!="usedeflang") and config.setupLocale():
+        restart(["usedeflang"])
+
+    setLanguage(config.getLanguage())
+    
+    from .gui.gui import GUI
+    gui = GUI(programDirectory, config)
+    
+    sys.stdout = StdIOHandler(gui)
+    sys.stderr = StdIOHandler(gui)
+
+    initializeSound(os.path.join(programDirectory, "sounds"))
+
+    try:
+        gui.build(programDirectory)
+
+        gui.run()
+    finally:
+        gui.flushStdIO()
+        sys.stdout = sys.__stdout__
+        sys.stderr = sys.__stderr__
+
+    config.save()
+
+    if gui.toRestart:
+        restart()
+
+#--------------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    main()
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 160)
+++ 	(revision )
@@ -1,81 +1,0 @@
-# The main program
-
-from config import Config
-from i18n import setLanguage
-from sound import initializeSound
-
-import os
-import sys
-
-#--------------------------------------------------------------------------------------
-
-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 restart(args = []):
-    """Restart the program with the given arguments."""
-    print "Restarting with args", args
-    programPath = os.path.join(os.path.dirname(sys.argv[0]),
-                               "runmlx.exe" if os.name=="nt" else "runmlx.sh")
-    if os.name=="nt":
-        import win32api
-        try:
-            programPath = win32api.GetShortPathName(programPath)
-        except:
-            programPath = os.path.join(os.path.dirname(sys.argv[0]),
-                                       "runmlx.bat")
-            programPath = win32api.GetShortPathName(programPath)
-
-    args = [programPath] + args
-
-    os.execv(programPath, args)    
-
-#--------------------------------------------------------------------------------------
-
-def main():
-    """The main operation of the program."""
-    programDirectory = os.path.dirname(sys.argv[0])
-
-    config = Config()
-    config.load()
-
-    if (len(sys.argv)<=1 or sys.argv[1]!="usedeflang") and config.setupLocale():
-        restart(["usedeflang"])
-
-    setLanguage(config.getLanguage())
-    
-    from .gui.gui import GUI
-    gui = GUI(programDirectory, config)
-    
-    sys.stdout = StdIOHandler(gui)
-    sys.stderr = StdIOHandler(gui)
-
-    initializeSound(os.path.join(programDirectory, "sounds"))
-
-    try:
-        gui.build(programDirectory)
-
-        gui.run()
-    finally:
-        gui.flushStdIO()
-        sys.stdout = sys.__stdout__
-        sys.stderr = sys.__stderr__
-
-    config.save()
-
-    if gui.toRestart:
-        restart()
-
-#--------------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    main()
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 151)
+++ src/mlx/pirep.py	(revision 151)
@@ -0,0 +1,140 @@
+# The PIREP class collecting all information needed for a PIREP
+
+#------------------------------------------------------------------------------
+
+import const
+import cPickle as pickle
+
+#------------------------------------------------------------------------------
+
+class PIREP(object):
+    """A pilot's report of a flight."""
+    _delayCodeNames = { const.DELAYCODE_LOADING : "Loading Problems",
+                        const.DELAYCODE_NETWORK : "Net Problems",
+                        const.DELAYCODE_SYSTEM : "System Crash/Freezing",
+                        const.DELAYCODE_TRAFFIC : "Traffic Problems",
+                        const.DELAYCODE_WEATHER : "Weather Problems",
+                        const.DELAYCODE_VATSIM : "VATSIM Problem",
+                        const.DELAYCODE_CONTROLLER : "Controller's Fault",
+                        const.DELAYCODE_NAVIGATION : "Navigation Problem",
+                        const.DELAYCODE_APRON : "Apron Navigation Problems",
+                        const.DELAYCODE_PERSONAL : "Personal Reasons" }
+
+    @staticmethod
+    def _formatLine(timeStr, line):
+        """Format the given time string and line as needed for the ACARS and
+        some other things."""
+        return "[" + timeStr + "]-[" + line + "]"
+
+    @staticmethod
+    def load(path):
+        """Load a PIREP from the given path.
+
+        Returns the PIREP object, or None on error."""
+        try:
+            with open(path, "rb") as f:
+                return pickle.load(f)
+        except Exception, e:
+            print "Failed loading PIREP from %s: %s" % (path, str(e))
+            return None
+        
+    def __init__(self, gui):
+        """Initialize the PIREP from the given GUI."""
+        self.bookedFlight = gui.bookedFlight
+        self.cargoWeight = gui.cargoWeight
+        
+        self.filedCruiseAltitude = gui.filedCruiseAltitude
+        self.cruiseAltitude = gui.cruiseAltitude
+        self.route = gui.route
+
+        self.departureMETAR = gui.departureMETAR.upper()
+        self.arrivalMETAR = gui.arrivalMETAR.upper()
+
+        self.departureRunway = gui.departureRunway.upper()
+        self.sid = gui.sid.upper()
+
+        self.star = gui.star
+        self.transition = gui.transition
+        self.approachType = gui.approachType.upper()
+        self.arrivalRunway = gui.arrivalRunway.upper()
+
+        self.flightType = gui.flightType
+        self.online = gui.online
+
+        self.comments = gui.comments
+        self.flightDefects = gui.flightDefects
+        self.delayCodes = gui.delayCodes
+        
+        flight = gui.flight
+        self.blockTimeStart = flight.blockTimeStart
+        self.flightTimeStart = flight.flightTimeStart
+        self.flightTimeEnd = flight.flightTimeEnd
+        self.blockTimeEnd = flight.blockTimeEnd
+        self.flownDistance = flight.flownDistance
+        self.fuelUsed = flight.startFuel - flight.endFuel
+
+        logger = gui.logger
+        self.rating = logger.getRating()
+        self.logLines = logger.lines
+        self.faultLineIndexes = logger.faultLineIndexes
+        
+    def getACARSText(self):
+        """Get the ACARS text.
+
+        This is a specially formatted version of the log without the faults."""
+        text = "[MAVA LOGGER X LOG]-[%s]" % (const.VERSION,)
+        for index in range(0, len(self.logLines)):
+            if index not in self.faultLineIndexes:
+                (timeStr, line) = self.logLines[index]
+                if timeStr is not None:
+                    text += PIREP._formatLine(timeStr, line)
+        return text
+
+    def getRatingText(self):
+        """Get the rating text.
+
+        This is a specially formatted version of the lines containing the
+        faults."""
+        text = ""
+        for index in self.faultLineIndexes:
+            (timeStr, line) = self.logLines[index]
+            if timeStr is not None:
+                text += PIREP._formatLine(timeStr, line)
+                text += "\n"            
+
+        text += "\n[Flight Rating: %.1f]" % (max(0.0, self.rating),)
+
+        return text
+    
+    def getTimeComment(self):
+        """Get the time comment.
+
+        This is basically a collection of the delay codes, if any."""
+        if not self.delayCodes:
+            return "UTC"
+        else:
+            s = ""
+            for code in self.delayCodes:
+                if s: s += ", "
+                s += PIREP._delayCodeNames[code]
+            return s
+
+    def getSTAR(self):
+        """Get the STAR and/or the transition."""
+        star = self.star if self.star is not None else ""
+        if self.transition is not None:
+            if star: star += ", "
+            star += self.transition
+        return star.upper()
+
+    def save(self, path):
+        """Save the PIREP to the given file.
+
+        Returns whether the saving has succeeded."""
+        try:
+            with open(path, "wb") as f:
+                pickle.dump(self, f)
+            return True
+        except Exception, e:
+            print "Failed saving PIREP to %s: %s" % (path, str(e))
+            return False
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 151)
+++ 	(revision )
@@ -1,140 +1,0 @@
-# The PIREP class collecting all information needed for a PIREP
-
-#------------------------------------------------------------------------------
-
-import const
-import cPickle as pickle
-
-#------------------------------------------------------------------------------
-
-class PIREP(object):
-    """A pilot's report of a flight."""
-    _delayCodeNames = { const.DELAYCODE_LOADING : "Loading Problems",
-                        const.DELAYCODE_NETWORK : "Net Problems",
-                        const.DELAYCODE_SYSTEM : "System Crash/Freezing",
-                        const.DELAYCODE_TRAFFIC : "Traffic Problems",
-                        const.DELAYCODE_WEATHER : "Weather Problems",
-                        const.DELAYCODE_VATSIM : "VATSIM Problem",
-                        const.DELAYCODE_CONTROLLER : "Controller's Fault",
-                        const.DELAYCODE_NAVIGATION : "Navigation Problem",
-                        const.DELAYCODE_APRON : "Apron Navigation Problems",
-                        const.DELAYCODE_PERSONAL : "Personal Reasons" }
-
-    @staticmethod
-    def _formatLine(timeStr, line):
-        """Format the given time string and line as needed for the ACARS and
-        some other things."""
-        return "[" + timeStr + "]-[" + line + "]"
-
-    @staticmethod
-    def load(path):
-        """Load a PIREP from the given path.
-
-        Returns the PIREP object, or None on error."""
-        try:
-            with open(path, "rb") as f:
-                return pickle.load(f)
-        except Exception, e:
-            print "Failed loading PIREP from %s: %s" % (path, str(e))
-            return None
-        
-    def __init__(self, gui):
-        """Initialize the PIREP from the given GUI."""
-        self.bookedFlight = gui.bookedFlight
-        self.cargoWeight = gui.cargoWeight
-        
-        self.filedCruiseAltitude = gui.filedCruiseAltitude
-        self.cruiseAltitude = gui.cruiseAltitude
-        self.route = gui.route
-
-        self.departureMETAR = gui.departureMETAR.upper()
-        self.arrivalMETAR = gui.arrivalMETAR.upper()
-
-        self.departureRunway = gui.departureRunway.upper()
-        self.sid = gui.sid.upper()
-
-        self.star = gui.star
-        self.transition = gui.transition
-        self.approachType = gui.approachType.upper()
-        self.arrivalRunway = gui.arrivalRunway.upper()
-
-        self.flightType = gui.flightType
-        self.online = gui.online
-
-        self.comments = gui.comments
-        self.flightDefects = gui.flightDefects
-        self.delayCodes = gui.delayCodes
-        
-        flight = gui.flight
-        self.blockTimeStart = flight.blockTimeStart
-        self.flightTimeStart = flight.flightTimeStart
-        self.flightTimeEnd = flight.flightTimeEnd
-        self.blockTimeEnd = flight.blockTimeEnd
-        self.flownDistance = flight.flownDistance
-        self.fuelUsed = flight.startFuel - flight.endFuel
-
-        logger = gui.logger
-        self.rating = logger.getRating()
-        self.logLines = logger.lines
-        self.faultLineIndexes = logger.faultLineIndexes
-        
-    def getACARSText(self):
-        """Get the ACARS text.
-
-        This is a specially formatted version of the log without the faults."""
-        text = "[MAVA LOGGER X LOG]-[%s]" % (const.VERSION,)
-        for index in range(0, len(self.logLines)):
-            if index not in self.faultLineIndexes:
-                (timeStr, line) = self.logLines[index]
-                if timeStr is not None:
-                    text += PIREP._formatLine(timeStr, line)
-        return text
-
-    def getRatingText(self):
-        """Get the rating text.
-
-        This is a specially formatted version of the lines containing the
-        faults."""
-        text = ""
-        for index in self.faultLineIndexes:
-            (timeStr, line) = self.logLines[index]
-            if timeStr is not None:
-                text += PIREP._formatLine(timeStr, line)
-                text += "\n"            
-
-        text += "\n[Flight Rating: %.1f]" % (max(0.0, self.rating),)
-
-        return text
-    
-    def getTimeComment(self):
-        """Get the time comment.
-
-        This is basically a collection of the delay codes, if any."""
-        if not self.delayCodes:
-            return "UTC"
-        else:
-            s = ""
-            for code in self.delayCodes:
-                if s: s += ", "
-                s += PIREP._delayCodeNames[code]
-            return s
-
-    def getSTAR(self):
-        """Get the STAR and/or the transition."""
-        star = self.star if self.star is not None else ""
-        if self.transition is not None:
-            if star: star += ", "
-            star += self.transition
-        return star.upper()
-
-    def save(self, path):
-        """Save the PIREP to the given file.
-
-        Returns whether the saving has succeeded."""
-        try:
-            with open(path, "wb") as f:
-                pickle.dump(self, f)
-            return True
-        except Exception, e:
-            print "Failed saving PIREP to %s: %s" % (path, str(e))
-            return False
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 163)
+++ src/mlx/pyuipc_sim.py	(revision 163)
@@ -0,0 +1,1290 @@
+# 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.latitude = 47.5
+        self.longitude = 19.05
+        
+        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.tdRate = 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.visibility = 10000
+        
+        self.n1 = [0.0, 0.0, 0.0]
+        self.throttles = [0.0, 0.0, 0.0]
+
+        self.payloadCount = 1
+        self.payload = []
+        for i in range(0, 61): self.payload.append(0.0)
+
+        self.textScrolling = False
+        self.message = ""
+        self.messageDuration = 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==0x030c:       # TD rate
+            return int(self.tdRate * 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==0x0560:       # Latitude
+            return long(self.latitude * 10001750.0 * 65536.0 * 65536.0 / 90.0)
+        elif offset==0x0568:       # Longitude
+            return long(self.longitude * 65536.0 * 65536.0 * 65536.0 * 65536.0 / 360.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(index * flapsIncrement +
+                           (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==0x0e8a:       # Visibility
+            return int(self.visibility * 100.0 / 1609.344)
+        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==0x1274:       # Text display mode
+            return 1 if self.textScrolling else 0
+        elif offset==0x13fc:       # The number of the payload stations
+            return self.payloadCount
+        elif offset>=0x1400 and offset<=0x1f40 and \
+             ((offset-0x1400)%48)==0: # Payload
+            return self.payload[ (offset - 0x1400) / 48 ]
+        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==0x32fa:       # Message duration
+            return self.messageDuration
+        elif offset==0x3380:       # Message
+            return self.message
+        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
+            if not self.onTheGround:
+                self.tdRate = self.vs
+        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:       # On the groud
+            self.onTheGround = value!=0
+            if not self.onTheGround:
+                self.tdRate = self.vs
+        elif offset==0x036c:       # Stalled
+            self.stalled = value!=0
+        elif offset==0x036d:       # Overspeed
+            self.overspeed = value!=0
+        elif offset==0x0560:       # Latitude
+            self.latitude = value * 90.0 / 10001750.0 / 65536.0 / 65536.0
+        elif offset==0x0568:       # Longitude
+            self.longitude = value * 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.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 = int(value / flapsIncrement)
+            if index>=numNotchesM1:
+                self.flapsControl = self.flapsNotches[-1]
+            else:
+                self.flapsControl = self.flapsNotches[index]
+                self.flapsControl += (value - index * flapsIncrement) * \
+                    (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 = (value&0x01)!=0
+            self.antiCollisionLightsOn = (value&0x02)!=0
+            self.landingLightsOn = (value&0x04)!=0 
+            self.strobeLightsOn = (value&0x10)!=0 
+        elif offset==0x0e8a:       # Visibility
+            self.visibility = value * 1609.344 / 100.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==0x1274:       # Text display mode
+            textScrolling = value!=0
+        elif offset==0x13fc:       # The number of the payload stations
+            self.payloadCount = int(value)
+        elif offset>=0x1400 and offset<=0x1f40 and \
+             ((offset-0x1400)%48)==0: # Payload
+            self.payload[ (offset - 0x1400) / 48 ] = 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==0x32fa:       # Message duration
+            self.messageDuration = value
+        elif offset==0x3380:       # Message
+            self.message = value
+        elif offset==0x3364:       # Frozen
+            self.frozen = value!=0
+        elif offset==0x3bfc:       # ZFW
+            self.zfw = value * const.LBSTOKG / 256.0
+        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
+
+        host = sys.argv[1] if len(sys.argv)>1 else "localhost"
+        self._client = Client(host)
+
+        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["latitude"] = (0x0560, "l",
+                                           lambda value: value * 90.0 /
+                                           10001750.0 / 65536.0 / 65536.0,
+                                           lambda word: long(float(word) *
+                                                             10001750.0 *
+                                                             65536.0 * 65536.0 / 90.0))
+        self._valueHandlers["longitude"] = (0x0568, "l",
+                                            lambda value: value * 360.0 /
+                                            65536.0 / 65536.0 / 65536.0 / 65536.0,
+                                            lambda word: long(float(word) *
+                                                              65536.0 * 65536.0 *
+                                                              65536.0 * 65536.0 /
+                                                              360.0))
+        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["tdRate"] = (0x030c, "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)
+                                                            
+        self._valueHandlers["visibility"] = (0x0e8a, "H",
+                                             lambda value: value*1609.344/100.0,
+                                             lambda word: int(float(word)*
+                                                              100.0/1609.344))
+                                                            
+        self._valueHandlers["payloadCount"] = (0x13fc, "d",
+                                               lambda value: value,
+                                               lambda word: int(word))
+        for i in range(0, 61):
+            self._valueHandlers["payload%d" % (i,)] = (0x1400 + i * 48, "f",
+                                                       lambda value:
+                                                       value * const.LBSTOKG,
+                                                       lambda word:
+                                                       float(word)*const.KGSTOLB)
+        self._valueHandlers["textScrolling"] = (0x1274, "h",
+                                                CLI.bool2str, CLI.str2bool)
+                                                            
+        self._valueHandlers["messageDuration"] = (0x32fa, "h",
+                                                  lambda value: value,
+                                                  lambda word: int(word))
+        self._valueHandlers["message"] = (0x3380, -128,
+                                          lambda value: value,
+                                          lambda word: word)
+    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 163)
+++ 	(revision )
@@ -1,1290 +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.latitude = 47.5
-        self.longitude = 19.05
-        
-        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.tdRate = 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.visibility = 10000
-        
-        self.n1 = [0.0, 0.0, 0.0]
-        self.throttles = [0.0, 0.0, 0.0]
-
-        self.payloadCount = 1
-        self.payload = []
-        for i in range(0, 61): self.payload.append(0.0)
-
-        self.textScrolling = False
-        self.message = ""
-        self.messageDuration = 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==0x030c:       # TD rate
-            return int(self.tdRate * 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==0x0560:       # Latitude
-            return long(self.latitude * 10001750.0 * 65536.0 * 65536.0 / 90.0)
-        elif offset==0x0568:       # Longitude
-            return long(self.longitude * 65536.0 * 65536.0 * 65536.0 * 65536.0 / 360.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(index * flapsIncrement +
-                           (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==0x0e8a:       # Visibility
-            return int(self.visibility * 100.0 / 1609.344)
-        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==0x1274:       # Text display mode
-            return 1 if self.textScrolling else 0
-        elif offset==0x13fc:       # The number of the payload stations
-            return self.payloadCount
-        elif offset>=0x1400 and offset<=0x1f40 and \
-             ((offset-0x1400)%48)==0: # Payload
-            return self.payload[ (offset - 0x1400) / 48 ]
-        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==0x32fa:       # Message duration
-            return self.messageDuration
-        elif offset==0x3380:       # Message
-            return self.message
-        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
-            if not self.onTheGround:
-                self.tdRate = self.vs
-        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:       # On the groud
-            self.onTheGround = value!=0
-            if not self.onTheGround:
-                self.tdRate = self.vs
-        elif offset==0x036c:       # Stalled
-            self.stalled = value!=0
-        elif offset==0x036d:       # Overspeed
-            self.overspeed = value!=0
-        elif offset==0x0560:       # Latitude
-            self.latitude = value * 90.0 / 10001750.0 / 65536.0 / 65536.0
-        elif offset==0x0568:       # Longitude
-            self.longitude = value * 360.0 / 65536.0 / 65536.0 / 65536.0 / 65536.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 = int(value / flapsIncrement)
-            if index>=numNotchesM1:
-                self.flapsControl = self.flapsNotches[-1]
-            else:
-                self.flapsControl = self.flapsNotches[index]
-                self.flapsControl += (value - index * flapsIncrement) * \
-                    (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 = (value&0x01)!=0
-            self.antiCollisionLightsOn = (value&0x02)!=0
-            self.landingLightsOn = (value&0x04)!=0 
-            self.strobeLightsOn = (value&0x10)!=0 
-        elif offset==0x0e8a:       # Visibility
-            self.visibility = value * 1609.344 / 100.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==0x1274:       # Text display mode
-            textScrolling = value!=0
-        elif offset==0x13fc:       # The number of the payload stations
-            self.payloadCount = int(value)
-        elif offset>=0x1400 and offset<=0x1f40 and \
-             ((offset-0x1400)%48)==0: # Payload
-            self.payload[ (offset - 0x1400) / 48 ] = 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==0x32fa:       # Message duration
-            self.messageDuration = value
-        elif offset==0x3380:       # Message
-            self.message = value
-        elif offset==0x3364:       # Frozen
-            self.frozen = value!=0
-        elif offset==0x3bfc:       # ZFW
-            self.zfw = value * const.LBSTOKG / 256.0
-        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
-
-        host = sys.argv[1] if len(sys.argv)>1 else "localhost"
-        self._client = Client(host)
-
-        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["latitude"] = (0x0560, "l",
-                                           lambda value: value * 90.0 /
-                                           10001750.0 / 65536.0 / 65536.0,
-                                           lambda word: long(float(word) *
-                                                             10001750.0 *
-                                                             65536.0 * 65536.0 / 90.0))
-        self._valueHandlers["longitude"] = (0x0568, "l",
-                                            lambda value: value * 360.0 /
-                                            65536.0 / 65536.0 / 65536.0 / 65536.0,
-                                            lambda word: long(float(word) *
-                                                              65536.0 * 65536.0 *
-                                                              65536.0 * 65536.0 /
-                                                              360.0))
-        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["tdRate"] = (0x030c, "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)
-                                                            
-        self._valueHandlers["visibility"] = (0x0e8a, "H",
-                                             lambda value: value*1609.344/100.0,
-                                             lambda word: int(float(word)*
-                                                              100.0/1609.344))
-                                                            
-        self._valueHandlers["payloadCount"] = (0x13fc, "d",
-                                               lambda value: value,
-                                               lambda word: int(word))
-        for i in range(0, 61):
-            self._valueHandlers["payload%d" % (i,)] = (0x1400 + i * 48, "f",
-                                                       lambda value:
-                                                       value * const.LBSTOKG,
-                                                       lambda word:
-                                                       float(word)*const.KGSTOLB)
-        self._valueHandlers["textScrolling"] = (0x1274, "h",
-                                                CLI.bool2str, CLI.str2bool)
-                                                            
-        self._valueHandlers["messageDuration"] = (0x32fa, "h",
-                                                  lambda value: value,
-                                                  lambda word: int(word))
-        self._valueHandlers["message"] = (0x3380, -128,
-                                          lambda value: value,
-                                          lambda word: word)
-    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/sound.py
===================================================================
--- src/mlx/sound.py	(revision 133)
+++ src/mlx/sound.py	(revision 133)
@@ -0,0 +1,169 @@
+# Module to handle sound playback
+
+#------------------------------------------------------------------------------
+
+import os
+
+#------------------------------------------------------------------------------
+
+if os.name=="nt":
+    import time
+    import threading
+    from ctypes import windll, c_buffer
+
+    class MCIException(Exception):
+        """MCI exception."""
+        def __init__(self, mci, command, errorCode):
+            """Construct an MCI exception for the given error code."""
+            message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode))
+            super(MCIException, self).__init__(message)
+
+    class MCI:
+        """Interface for the Media Control Interface."""
+        def __init__(self):
+            """Construct the interface."""
+            self.w32mci = windll.winmm.mciSendStringA
+            self.w32mcierror = windll.winmm.mciGetErrorStringA
+
+        def send(self, command):
+            """Send the given command to the MCI."""
+            buffer = c_buffer(255)
+            errorCode = self.w32mci(str(command), buffer, 254, 0)
+            if errorCode:
+                raise MCIException(self, command, errorCode)
+            else:
+                return buffer.value
+
+        def getErrorString(self, errorCode):
+            """Get the string representation of the given error code."""
+            buffer = c_buffer(255)
+            self.w32mcierror(int(errorCode), buffer, 254)
+            return buffer.value
+
+    class SoundThread(threading.Thread):
+        """The thread controlling the playback of sounds."""
+        def __init__(self, soundsDirectory):
+            threading.Thread.__init__(self)
+
+            self._soundsDirectory = soundsDirectory
+            self._mci = MCI()
+
+            self._requestCondition = threading.Condition()
+            self._requestedPaths = []
+            self._pendingAliases = []
+            self._count = 0
+
+            self.daemon = True
+
+        def requestSound(self, name):
+            """Request the playback of the sound with the given name."""
+            path = os.path.join(self._soundsDirectory, name)
+            with self._requestCondition:
+                self._requestedPaths.append(path)
+                self._requestCondition.notify()
+
+        def run(self):
+            """Perform the operation of the thread.
+
+            It waits for a request or a timeout. If a request is received, that
+            is started to be played back. If a timeout occurs, the file is
+            closed."""
+
+            while True:
+                with self._requestCondition:
+                    if not self._requestedPaths:
+                        if self._pendingAliases:
+                            timeout = max(time.time() -
+                                          self._pendingAliases[0][0], 0.0)
+                        else:
+                            timeout = 10.0
+                            
+                        self._requestCondition.wait(timeout)
+
+                    requestedPaths = []
+                    for path in self._requestedPaths:
+                        requestedPaths.append((path, self._count))
+                        self._count += 1
+                    self._requestedPaths = []
+
+                    now = time.time()
+                    aliasesToClose = []
+                    while self._pendingAliases and \
+                          self._pendingAliases[0][0]<=now:
+                        aliasesToClose.append(self._pendingAliases[0][1])
+                        del self._pendingAliases[0]
+
+                for alias in aliasesToClose:
+                    try:
+                        print "Closing", alias
+                        self._mci.send("close " + alias)
+                        print "Closed", alias
+                    except Exception, e:
+                        print "Failed closing " + alias + ":", str(e)
+
+                for (path, counter) in requestedPaths:
+                    try:
+                        alias = "mlxsound%d" % (counter,)
+                        print "Starting to play", path, "as", alias
+                        self._mci.send("open \"%s\" alias %s" % \
+                                       (path, alias))
+                        self._mci.send("set %s time format milliseconds" % \
+                                       (alias,))
+                        lengthBuffer = self._mci.send("status %s length" % \
+                                                      (alias,))
+                        self._mci.send("play %s from 0 to %s" % \
+                                       (alias, lengthBuffer))
+                        length = int(lengthBuffer)
+                        timeout = time.time() + length / 1000.0
+                        with self._requestCondition:
+                            self._pendingAliases.append((timeout, alias))
+                            self._pendingAliases.sort()
+                        print "Started to play", path
+                    except Exception, e:
+                        print "Failed to start playing " + path + ":", str(e)
+
+    _thread = None
+
+    def initializeSound(soundsDirectory):
+        """Initialize the sound handling with the given directory containing
+        the sound files."""
+        global _thread
+        _thread = SoundThread(soundsDirectory)
+        _thread.start()
+
+    def startSound(name):
+        """Start playing back the given sound.
+        
+        name should be the name of a sound file relative to the sound directory
+        given in initializeSound."""
+        _thread.requestSound(name)
+        
+#------------------------------------------------------------------------------
+
+else: # os.name!="nt"
+    def initializeSound(soundsDirectory):
+        """Initialize the sound handling with the given directory containing
+        the sound files."""
+        pass
+
+    def startSound(name):
+        """Start playing back the given sound.
+
+        FIXME: it does not do anything currently, but it should."""
+        pass
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    initializeSound("e:\\home\\vi\\tmp")
+    startSound("malev.mp3")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(5)
+    startSound("ding.wav")
+    time.sleep(50)
+
+#------------------------------------------------------------------------------
Index: src/mlx/sound.py
===================================================================
--- src/mlx/sound.py	(revision 133)
+++ 	(revision )
@@ -1,169 +1,0 @@
-# Module to handle sound playback
-
-#------------------------------------------------------------------------------
-
-import os
-
-#------------------------------------------------------------------------------
-
-if os.name=="nt":
-    import time
-    import threading
-    from ctypes import windll, c_buffer
-
-    class MCIException(Exception):
-        """MCI exception."""
-        def __init__(self, mci, command, errorCode):
-            """Construct an MCI exception for the given error code."""
-            message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode))
-            super(MCIException, self).__init__(message)
-
-    class MCI:
-        """Interface for the Media Control Interface."""
-        def __init__(self):
-            """Construct the interface."""
-            self.w32mci = windll.winmm.mciSendStringA
-            self.w32mcierror = windll.winmm.mciGetErrorStringA
-
-        def send(self, command):
-            """Send the given command to the MCI."""
-            buffer = c_buffer(255)
-            errorCode = self.w32mci(str(command), buffer, 254, 0)
-            if errorCode:
-                raise MCIException(self, command, errorCode)
-            else:
-                return buffer.value
-
-        def getErrorString(self, errorCode):
-            """Get the string representation of the given error code."""
-            buffer = c_buffer(255)
-            self.w32mcierror(int(errorCode), buffer, 254)
-            return buffer.value
-
-    class SoundThread(threading.Thread):
-        """The thread controlling the playback of sounds."""
-        def __init__(self, soundsDirectory):
-            threading.Thread.__init__(self)
-
-            self._soundsDirectory = soundsDirectory
-            self._mci = MCI()
-
-            self._requestCondition = threading.Condition()
-            self._requestedPaths = []
-            self._pendingAliases = []
-            self._count = 0
-
-            self.daemon = True
-
-        def requestSound(self, name):
-            """Request the playback of the sound with the given name."""
-            path = os.path.join(self._soundsDirectory, name)
-            with self._requestCondition:
-                self._requestedPaths.append(path)
-                self._requestCondition.notify()
-
-        def run(self):
-            """Perform the operation of the thread.
-
-            It waits for a request or a timeout. If a request is received, that
-            is started to be played back. If a timeout occurs, the file is
-            closed."""
-
-            while True:
-                with self._requestCondition:
-                    if not self._requestedPaths:
-                        if self._pendingAliases:
-                            timeout = max(time.time() -
-                                          self._pendingAliases[0][0], 0.0)
-                        else:
-                            timeout = 10.0
-                            
-                        self._requestCondition.wait(timeout)
-
-                    requestedPaths = []
-                    for path in self._requestedPaths:
-                        requestedPaths.append((path, self._count))
-                        self._count += 1
-                    self._requestedPaths = []
-
-                    now = time.time()
-                    aliasesToClose = []
-                    while self._pendingAliases and \
-                          self._pendingAliases[0][0]<=now:
-                        aliasesToClose.append(self._pendingAliases[0][1])
-                        del self._pendingAliases[0]
-
-                for alias in aliasesToClose:
-                    try:
-                        print "Closing", alias
-                        self._mci.send("close " + alias)
-                        print "Closed", alias
-                    except Exception, e:
-                        print "Failed closing " + alias + ":", str(e)
-
-                for (path, counter) in requestedPaths:
-                    try:
-                        alias = "mlxsound%d" % (counter,)
-                        print "Starting to play", path, "as", alias
-                        self._mci.send("open \"%s\" alias %s" % \
-                                       (path, alias))
-                        self._mci.send("set %s time format milliseconds" % \
-                                       (alias,))
-                        lengthBuffer = self._mci.send("status %s length" % \
-                                                      (alias,))
-                        self._mci.send("play %s from 0 to %s" % \
-                                       (alias, lengthBuffer))
-                        length = int(lengthBuffer)
-                        timeout = time.time() + length / 1000.0
-                        with self._requestCondition:
-                            self._pendingAliases.append((timeout, alias))
-                            self._pendingAliases.sort()
-                        print "Started to play", path
-                    except Exception, e:
-                        print "Failed to start playing " + path + ":", str(e)
-
-    _thread = None
-
-    def initializeSound(soundsDirectory):
-        """Initialize the sound handling with the given directory containing
-        the sound files."""
-        global _thread
-        _thread = SoundThread(soundsDirectory)
-        _thread.start()
-
-    def startSound(name):
-        """Start playing back the given sound.
-        
-        name should be the name of a sound file relative to the sound directory
-        given in initializeSound."""
-        _thread.requestSound(name)
-        
-#------------------------------------------------------------------------------
-
-else: # os.name!="nt"
-    def initializeSound(soundsDirectory):
-        """Initialize the sound handling with the given directory containing
-        the sound files."""
-        pass
-
-    def startSound(name):
-        """Start playing back the given sound.
-
-        FIXME: it does not do anything currently, but it should."""
-        pass
-
-#------------------------------------------------------------------------------
-#------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    initializeSound("e:\\home\\vi\\tmp")
-    startSound("malev.mp3")
-    time.sleep(5)
-    startSound("ding.wav")
-    time.sleep(5)
-    startSound("ding.wav")
-    time.sleep(5)
-    startSound("ding.wav")
-    time.sleep(50)
-
-#------------------------------------------------------------------------------
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 129)
+++ src/mlx/update.py	(revision 129)
@@ -0,0 +1,585 @@
+# 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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        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()
+    config.load()
+
+    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 129)
+++ 	(revision )
@@ -1,585 +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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        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()
-    config.load()
-
-    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 134)
+++ src/mlx/util.py	(revision 134)
@@ -0,0 +1,156 @@
+# Various utilities
+
+import math
+import time
+
+#------------------------------------------------------------------------------
+
+# The average of the radius at the poles and a the equator, in metres
+#EARTH_RADIUS=6367467.4
+EARTH_RADIUS=6371000
+#EARTH_RADIUS=6378137
+
+#------------------------------------------------------------------------------
+
+def getDegMinSec(degrees):
+    """Break up the given floating point degrees value into a tuple.
+
+    The tuple contains 4 items:
+    - the degrees as an integer
+    - the minutes as an integer
+    - the seconds as an integer
+    - 1.0 if the value was non-negative, -1.0 if it was negative."""
+    
+    if degrees<0:
+        degrees = -degrees
+        mul = -1.0
+    else:
+        mul = 1.0
+        
+    deg = int(degrees)
+    min = int((degrees*60.0)%60.0)
+    sec = int((degrees*3600.0)%60.0)
+
+    return (deg, min, sec, mul)
+
+#------------------------------------------------------------------------------
+
+def getCoordinateString((latitude, longitude)):
+    """Get the string representation of the given coordinate pair."""
+
+    latitude_str = getLatitudeString(latitude)
+    longitude_str = getLongitudeString(longitude)
+
+    return latitude_str + " " + longitude_str
+
+#------------------------------------------------------------------------------
+
+def getLatitudeString(latitude):
+    """Get a string representation of the given latitude."""
+    return getDegreeString(latitude, ["N", "S"])
+
+#------------------------------------------------------------------------------
+
+def getLongitudeString(longitude):
+    """Get a string representation of the given longitude."""
+
+    return getDegreeString(longitude, ["E", "W"])
+
+#------------------------------------------------------------------------------
+
+def getDegreeString(degree, prefixes):
+    """Get a string representation of the given degree.
+
+    If the sign is positive, prefixes[0], otherwise prefixes[1] will be
+    prepended to the string."""
+
+    if degree<0:
+        prefix = prefixes[1]
+    else:
+        prefix = prefixes[0]
+
+    (deg, min, sec, _sign) = getDegMinSec(degree)
+
+    return u"%s%d\u00b0%02d\u2032%02d\u2033" % (prefix, deg, min, sec)
+
+#------------------------------------------------------------------------------
+
+def getTimestampString(timestamp):
+    """Get the string representation of the given timestamp."""
+    return time.strftime("%H:%M:%S", time.gmtime(timestamp))
+
+#------------------------------------------------------------------------------
+
+def getTimeIntervalString(seconds):
+    """Get a more human-friendly representation of the given time interval
+    expressed in seconds."""
+    hours = int(seconds / 3600)
+    minutes = int((seconds / 60) % 60)
+    seconds = int(seconds % 60)
+    return "%02d:%02d:%02d" % (hours, minutes, seconds)
+
+#------------------------------------------------------------------------------
+
+def km2nm(km):
+    """Convert the given kilometres into nautical miles."""
+    return km/1.852
+
+#------------------------------------------------------------------------------
+
+def nm2km(nm):
+    """Convert the given nautical miles into kilometres."""
+    return nm*1.852
+
+#------------------------------------------------------------------------------
+
+def radians2km(radians):
+    """Convert the given radians into kilometres"""
+    return radians * EARTH_RADIUS / 1000.0
+
+#------------------------------------------------------------------------------
+
+def radians2nm(radians):
+    """Convert the given radians into nautical miles."""
+    return km2nm(radians2km(radians))
+
+#------------------------------------------------------------------------------
+
+def getDistCourse(latitude1, longitude1, latitude2, longitude2):
+    """Get the distance and course between the two geographical coordinates.
+
+    This function calculates the rhumb distance."""
+    
+    latitude1 = math.radians(latitude1)
+    longitude1 = math.radians(longitude1)
+
+    latitude2 = math.radians(latitude2)
+    longitude2 = math.radians(longitude2)
+
+    dlon_W = (longitude1 - longitude2) % (math.pi*2)
+    dlon_E = (longitude2 - longitude1) % (math.pi*2)
+
+    dphi = math.log(math.tan(latitude2/2 + math.pi/4)/
+                    math.tan(latitude1/2 + math.pi/4))
+
+    if abs(latitude1-latitude2) < math.sqrt(1e-15):
+        q = math.cos(latitude1)
+    else:
+        q = (latitude1-latitude2)/dphi
+
+    if dlon_W < dlon_E:
+        tc = math.atan2(-dlon_W, dphi) % (math.pi*2)
+        d = math.sqrt(math.pow(q*dlon_W, 2) +
+                      math.pow(latitude1-latitude2, 2))
+    else:
+        tc = math.atan2(dlon_E, dphi) % (math.pi*2)
+        d = math.sqrt(math.pow(q*dlon_E, 2) +
+                      math.pow(latitude1-latitude2, 2))
+
+    return (radians2nm(d), math.degrees(tc))
+
+#------------------------------------------------------------------------------
+
+def visibility2String(visibility):
+    """Convert the given visibility expressed in metres into a string."""
+    return "%.0f metres" % (visibility,) if visibility<10000 \
+           else "%.1f kilometres" % (visibility/1000.0,)
Index: src/mlx/util.py
===================================================================
--- src/mlx/util.py	(revision 134)
+++ 	(revision )
@@ -1,156 +1,0 @@
-# Various utilities
-
-import math
-import time
-
-#------------------------------------------------------------------------------
-
-# The average of the radius at the poles and a the equator, in metres
-#EARTH_RADIUS=6367467.4
-EARTH_RADIUS=6371000
-#EARTH_RADIUS=6378137
-
-#------------------------------------------------------------------------------
-
-def getDegMinSec(degrees):
-    """Break up the given floating point degrees value into a tuple.
-
-    The tuple contains 4 items:
-    - the degrees as an integer
-    - the minutes as an integer
-    - the seconds as an integer
-    - 1.0 if the value was non-negative, -1.0 if it was negative."""
-    
-    if degrees<0:
-        degrees = -degrees
-        mul = -1.0
-    else:
-        mul = 1.0
-        
-    deg = int(degrees)
-    min = int((degrees*60.0)%60.0)
-    sec = int((degrees*3600.0)%60.0)
-
-    return (deg, min, sec, mul)
-
-#------------------------------------------------------------------------------
-
-def getCoordinateString((latitude, longitude)):
-    """Get the string representation of the given coordinate pair."""
-
-    latitude_str = getLatitudeString(latitude)
-    longitude_str = getLongitudeString(longitude)
-
-    return latitude_str + " " + longitude_str
-
-#------------------------------------------------------------------------------
-
-def getLatitudeString(latitude):
-    """Get a string representation of the given latitude."""
-    return getDegreeString(latitude, ["N", "S"])
-
-#------------------------------------------------------------------------------
-
-def getLongitudeString(longitude):
-    """Get a string representation of the given longitude."""
-
-    return getDegreeString(longitude, ["E", "W"])
-
-#------------------------------------------------------------------------------
-
-def getDegreeString(degree, prefixes):
-    """Get a string representation of the given degree.
-
-    If the sign is positive, prefixes[0], otherwise prefixes[1] will be
-    prepended to the string."""
-
-    if degree<0:
-        prefix = prefixes[1]
-    else:
-        prefix = prefixes[0]
-
-    (deg, min, sec, _sign) = getDegMinSec(degree)
-
-    return u"%s%d\u00b0%02d\u2032%02d\u2033" % (prefix, deg, min, sec)
-
-#------------------------------------------------------------------------------
-
-def getTimestampString(timestamp):
-    """Get the string representation of the given timestamp."""
-    return time.strftime("%H:%M:%S", time.gmtime(timestamp))
-
-#------------------------------------------------------------------------------
-
-def getTimeIntervalString(seconds):
-    """Get a more human-friendly representation of the given time interval
-    expressed in seconds."""
-    hours = int(seconds / 3600)
-    minutes = int((seconds / 60) % 60)
-    seconds = int(seconds % 60)
-    return "%02d:%02d:%02d" % (hours, minutes, seconds)
-
-#------------------------------------------------------------------------------
-
-def km2nm(km):
-    """Convert the given kilometres into nautical miles."""
-    return km/1.852
-
-#------------------------------------------------------------------------------
-
-def nm2km(nm):
-    """Convert the given nautical miles into kilometres."""
-    return nm*1.852
-
-#------------------------------------------------------------------------------
-
-def radians2km(radians):
-    """Convert the given radians into kilometres"""
-    return radians * EARTH_RADIUS / 1000.0
-
-#------------------------------------------------------------------------------
-
-def radians2nm(radians):
-    """Convert the given radians into nautical miles."""
-    return km2nm(radians2km(radians))
-
-#------------------------------------------------------------------------------
-
-def getDistCourse(latitude1, longitude1, latitude2, longitude2):
-    """Get the distance and course between the two geographical coordinates.
-
-    This function calculates the rhumb distance."""
-    
-    latitude1 = math.radians(latitude1)
-    longitude1 = math.radians(longitude1)
-
-    latitude2 = math.radians(latitude2)
-    longitude2 = math.radians(longitude2)
-
-    dlon_W = (longitude1 - longitude2) % (math.pi*2)
-    dlon_E = (longitude2 - longitude1) % (math.pi*2)
-
-    dphi = math.log(math.tan(latitude2/2 + math.pi/4)/
-                    math.tan(latitude1/2 + math.pi/4))
-
-    if abs(latitude1-latitude2) < math.sqrt(1e-15):
-        q = math.cos(latitude1)
-    else:
-        q = (latitude1-latitude2)/dphi
-
-    if dlon_W < dlon_E:
-        tc = math.atan2(-dlon_W, dphi) % (math.pi*2)
-        d = math.sqrt(math.pow(q*dlon_W, 2) +
-                      math.pow(latitude1-latitude2, 2))
-    else:
-        tc = math.atan2(dlon_E, dphi) % (math.pi*2)
-        d = math.sqrt(math.pow(q*dlon_E, 2) +
-                      math.pow(latitude1-latitude2, 2))
-
-    return (radians2nm(d), math.degrees(tc))
-
-#------------------------------------------------------------------------------
-
-def visibility2String(visibility):
-    """Convert the given visibility expressed in metres into a string."""
-    return "%.0f metres" % (visibility,) if visibility<10000 \
-           else "%.1f kilometres" % (visibility/1000.0,)
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 151)
+++ src/mlx/web.py	(revision 151)
@@ -0,0 +1,811 @@
+# Interface towards the websites used
+
+#------------------------------------------------------------------------------
+
+import const
+import util
+
+import threading
+import sys
+import urllib
+import urllib2
+import hashlib
+import time
+import datetime
+import codecs
+import traceback
+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 }
+
+    TYPE2TYPECODE = { const.AIRCRAFT_B736 : "736",
+                      const.AIRCRAFT_B737 : "73G",
+                      const.AIRCRAFT_B738 : "738",
+                      const.AIRCRAFT_B733 : "733",
+                      const.AIRCRAFT_B734 : "734",
+                      const.AIRCRAFT_B735 : "735",
+                      const.AIRCRAFT_DH8D : "DH4",
+                      const.AIRCRAFT_B762 : "762",
+                      const.AIRCRAFT_B763 : "763",
+                      const.AIRCRAFT_CRJ2 : "CR2",
+                      const.AIRCRAFT_F70  : "F70",
+                      const.AIRCRAFT_DC3  : "LI2",
+                      const.AIRCRAFT_T134 : "TU3",
+                      const.AIRCRAFT_T154 : "TU5",
+                      const.AIRCRAFT_YK40 : "YK4" }
+    
+    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 updatePlane(self, tailNumber, status, gateNumber = None):
+        """Update the status of the given plane."""
+        if tailNumber in self._planes:
+            plane = self._planes[tailNumber]
+            plane.status = status
+            plane.gateNumber = gateNumber
+    
+    def __iter__(self):
+        """Get an iterator over the planes."""
+        for plane in self._planes.itervalues():
+            yield plane
+        
+    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:
+            traceback.print_exc()
+            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)
+            traceback.print_exc()
+
+#------------------------------------------------------------------------------
+
+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, timeout = 10.0)
+
+        status = readline(f)
+        result.loggedIn = status == ".OK."
+
+        if result.loggedIn:
+            result.pilotID = self._pilotID
+            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, timeout = 10.0)
+        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, timeout = 10.0)
+        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, timeout = 10.0)
+        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, timeout = 10.0)
+        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 SendPIREP(Request):
+    """A request to send a PIREP to the MAVA website."""
+    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
+                     const.FLIGHTTYPE_OLDTIMER : "OT",
+                     const.FLIGHTTYPE_VIP : "VIP",
+                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
+
+    _latin2Encoder = codecs.getencoder("iso-8859-2")
+
+    def __init__(self, callback, pirep):
+        """Construct the request for the given PIREP."""
+        super(SendPIREP, self).__init__(callback)
+        self._pirep = pirep
+
+    def run(self):
+        """Perform the retrieval opf the METARs."""
+        url = "http://www.virtualairlines.hu/malevacars.php"
+
+        pirep = self._pirep
+
+        data = {}
+        data["acarsdata"] = pirep.getACARSText()
+
+        bookedFlight = pirep.bookedFlight
+        data["foglalas_id"] = bookedFlight.id
+        data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
+        data["fltnum"] = bookedFlight.callsign
+        data["depap"] = bookedFlight.departureICAO
+        data["arrap"] = bookedFlight.arrivalICAO
+        data["pass"] = str(bookedFlight.numPassengers)
+        data["crew"] = str(bookedFlight.numCrew)
+        data["cargo"] = str(pirep.cargoWeight)
+        data["bag"] = str(bookedFlight.bagWeight)
+        data["mail"] = str(bookedFlight.mailWeight)
+        
+        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
+        data["onoff"] = "1" if pirep.online else "0"
+        data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
+        data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
+        data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
+                                                    pirep.blockTimeStart)
+        data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
+        data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
+        data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
+                                                    pirep.flightTimeStart)
+        data["timecomm"] = pirep.getTimeComment()
+        data["fuel"] = "%.0f" % (pirep.fuelUsed,)
+        data["dep_rwy"] = pirep.departureRunway
+        data["arr_rwy"] = pirep.arrivalRunway
+        data["wea_dep"] = pirep.departureMETAR
+        data["wea_arr"] = pirep.arrivalMETAR
+        data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
+        if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
+            data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
+        else:
+            data["mod_alt"] = ""
+        data["sid"] = pirep.sid
+        data["navroute"] = pirep.route
+        data["star"] = pirep.getSTAR()
+        data["aprtype"] = pirep.approachType
+        data["diff"] = "2"
+        data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
+        data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
+        data["kritika"] = pirep.getRatingText()
+        data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
+        data["distance"] = "%.3f" % (pirep.flownDistance,)
+        data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
+
+        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
+        try:
+            result = Result()
+            line = f.readline().strip()
+            print "PIREP result from website:", line
+            result.success = line=="OK"
+            result.alreadyFlown = line=="MARVOLT"
+            result.notAvailable = line=="NOMORE"
+        finally:
+            f.close()
+
+        return result    
+
+#------------------------------------------------------------------------------
+
+class SendPIREP(Request):
+    """A request to send a PIREP to the MAVA website."""
+    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
+                     const.FLIGHTTYPE_OLDTIMER : "OT",
+                     const.FLIGHTTYPE_VIP : "VIP",
+                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
+
+    _latin2Encoder = codecs.getencoder("iso-8859-2")
+
+    def __init__(self, callback, pirep):
+        """Construct the request for the given PIREP."""
+        super(SendPIREP, self).__init__(callback)
+        self._pirep = pirep
+
+    def run(self):
+        """Perform the sending of the PIREP."""
+        url = "http://www.virtualairlines.hu/malevacars.php"
+
+        pirep = self._pirep
+
+        data = {}
+        data["acarsdata"] = pirep.getACARSText()
+
+        bookedFlight = pirep.bookedFlight
+        data["foglalas_id"] = bookedFlight.id
+        data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
+        data["fltnum"] = bookedFlight.callsign
+        data["depap"] = bookedFlight.departureICAO
+        data["arrap"] = bookedFlight.arrivalICAO
+        data["pass"] = str(bookedFlight.numPassengers)
+        data["crew"] = str(bookedFlight.numCrew)
+        data["cargo"] = str(pirep.cargoWeight)
+        data["bag"] = str(bookedFlight.bagWeight)
+        data["mail"] = str(bookedFlight.mailWeight)
+        
+        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
+        data["onoff"] = "1" if pirep.online else "0"
+        data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
+        data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
+        data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
+                                                    pirep.blockTimeStart)
+        data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
+        data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
+        data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
+                                                    pirep.flightTimeStart)
+        data["timecomm"] = pirep.getTimeComment()
+        data["fuel"] = "%.0f" % (pirep.fuelUsed,)
+        data["dep_rwy"] = pirep.departureRunway
+        data["arr_rwy"] = pirep.arrivalRunway
+        data["wea_dep"] = pirep.departureMETAR
+        data["wea_arr"] = pirep.arrivalMETAR
+        data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
+        if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
+            data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
+        else:
+            data["mod_alt"] = ""
+        data["sid"] = pirep.sid
+        data["navroute"] = pirep.route
+        data["star"] = pirep.getSTAR()
+        data["aprtype"] = pirep.approachType
+        data["diff"] = "2"
+        data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
+        data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
+        data["kritika"] = pirep.getRatingText()
+        data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
+        data["distance"] = "%.3f" % (pirep.flownDistance,)
+        data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
+
+        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
+        try:
+            result = Result()
+            line = f.readline().strip()
+            print "PIREP result from website:", line
+            result.success = line=="OK"
+            result.alreadyFlown = line=="MARVOLT"
+            result.notAvailable = line=="NOMORE"
+        finally:
+            f.close()
+
+        return result    
+
+#------------------------------------------------------------------------------
+
+class SendACARS(Request):
+    """A request to send an ACARS to the MAVA website."""
+    _latin2Encoder = codecs.getencoder("iso-8859-2")
+
+    def __init__(self, callback, acars):
+        """Construct the request for the given PIREP."""
+        super(SendACARS, self).__init__(callback)
+        self._acars = acars
+
+    def run(self):
+        """Perform the sending of the ACARS."""
+        url = "http://www.virtualairlines.hu/acars2/acarsonline.php"
+
+        acars = self._acars
+        bookedFlight = acars.bookedFlight
+
+        data = {}
+        data["pid"] = acars.pid
+        data["pilot"] = SendACARS._latin2Encoder(acars.pilotName)[0]
+    
+        data["pass"] = str(bookedFlight.numPassengers)
+        data["callsign"] = bookedFlight.callsign
+        data["airplane"] = BookedFlight.TYPE2TYPECODE[bookedFlight.aircraftType]
+        data["from"] = bookedFlight.departureICAO
+        data["to"] = bookedFlight.arrivalICAO        
+        data["lajstrom"] = bookedFlight.tailNumber
+
+        data["block_time"] = acars.getBlockTimeText()
+        data["longitude"] = str(acars.state.longitude)
+        data["latitude"] = str(acars.state.latitude)
+        data["altitude"] = str(acars.state.altitude)
+        data["speed"] = str(acars.state.groundSpeed)
+        
+        data["event"] = acars.getEventText()
+
+        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
+        try:
+            result = Result()
+        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 sendPIREP(self, callback, pirep):
+        """Send the given PIREP."""
+        self._addRequest(SendPIREP(callback, pirep))
+
+    def sendACARS(self, callback, acars):
+        """Send the given ACARS"""
+        self._addRequest(SendACARS(callback, acars))
+        
+    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 151)
+++ 	(revision )
@@ -1,811 +1,0 @@
-# Interface towards the websites used
-
-#------------------------------------------------------------------------------
-
-import const
-import util
-
-import threading
-import sys
-import urllib
-import urllib2
-import hashlib
-import time
-import datetime
-import codecs
-import traceback
-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 }
-
-    TYPE2TYPECODE = { const.AIRCRAFT_B736 : "736",
-                      const.AIRCRAFT_B737 : "73G",
-                      const.AIRCRAFT_B738 : "738",
-                      const.AIRCRAFT_B733 : "733",
-                      const.AIRCRAFT_B734 : "734",
-                      const.AIRCRAFT_B735 : "735",
-                      const.AIRCRAFT_DH8D : "DH4",
-                      const.AIRCRAFT_B762 : "762",
-                      const.AIRCRAFT_B763 : "763",
-                      const.AIRCRAFT_CRJ2 : "CR2",
-                      const.AIRCRAFT_F70  : "F70",
-                      const.AIRCRAFT_DC3  : "LI2",
-                      const.AIRCRAFT_T134 : "TU3",
-                      const.AIRCRAFT_T154 : "TU5",
-                      const.AIRCRAFT_YK40 : "YK4" }
-    
-    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 updatePlane(self, tailNumber, status, gateNumber = None):
-        """Update the status of the given plane."""
-        if tailNumber in self._planes:
-            plane = self._planes[tailNumber]
-            plane.status = status
-            plane.gateNumber = gateNumber
-    
-    def __iter__(self):
-        """Get an iterator over the planes."""
-        for plane in self._planes.itervalues():
-            yield plane
-        
-    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:
-            traceback.print_exc()
-            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)
-            traceback.print_exc()
-
-#------------------------------------------------------------------------------
-
-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, timeout = 10.0)
-
-        status = readline(f)
-        result.loggedIn = status == ".OK."
-
-        if result.loggedIn:
-            result.pilotID = self._pilotID
-            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, timeout = 10.0)
-        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, timeout = 10.0)
-        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, timeout = 10.0)
-        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, timeout = 10.0)
-        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 SendPIREP(Request):
-    """A request to send a PIREP to the MAVA website."""
-    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
-                     const.FLIGHTTYPE_OLDTIMER : "OT",
-                     const.FLIGHTTYPE_VIP : "VIP",
-                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
-
-    _latin2Encoder = codecs.getencoder("iso-8859-2")
-
-    def __init__(self, callback, pirep):
-        """Construct the request for the given PIREP."""
-        super(SendPIREP, self).__init__(callback)
-        self._pirep = pirep
-
-    def run(self):
-        """Perform the retrieval opf the METARs."""
-        url = "http://www.virtualairlines.hu/malevacars.php"
-
-        pirep = self._pirep
-
-        data = {}
-        data["acarsdata"] = pirep.getACARSText()
-
-        bookedFlight = pirep.bookedFlight
-        data["foglalas_id"] = bookedFlight.id
-        data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
-        data["fltnum"] = bookedFlight.callsign
-        data["depap"] = bookedFlight.departureICAO
-        data["arrap"] = bookedFlight.arrivalICAO
-        data["pass"] = str(bookedFlight.numPassengers)
-        data["crew"] = str(bookedFlight.numCrew)
-        data["cargo"] = str(pirep.cargoWeight)
-        data["bag"] = str(bookedFlight.bagWeight)
-        data["mail"] = str(bookedFlight.mailWeight)
-        
-        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
-        data["onoff"] = "1" if pirep.online else "0"
-        data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
-        data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
-        data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
-                                                    pirep.blockTimeStart)
-        data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
-        data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
-        data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
-                                                    pirep.flightTimeStart)
-        data["timecomm"] = pirep.getTimeComment()
-        data["fuel"] = "%.0f" % (pirep.fuelUsed,)
-        data["dep_rwy"] = pirep.departureRunway
-        data["arr_rwy"] = pirep.arrivalRunway
-        data["wea_dep"] = pirep.departureMETAR
-        data["wea_arr"] = pirep.arrivalMETAR
-        data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
-        if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
-            data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
-        else:
-            data["mod_alt"] = ""
-        data["sid"] = pirep.sid
-        data["navroute"] = pirep.route
-        data["star"] = pirep.getSTAR()
-        data["aprtype"] = pirep.approachType
-        data["diff"] = "2"
-        data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
-        data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
-        data["kritika"] = pirep.getRatingText()
-        data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
-        data["distance"] = "%.3f" % (pirep.flownDistance,)
-        data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
-
-        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
-        try:
-            result = Result()
-            line = f.readline().strip()
-            print "PIREP result from website:", line
-            result.success = line=="OK"
-            result.alreadyFlown = line=="MARVOLT"
-            result.notAvailable = line=="NOMORE"
-        finally:
-            f.close()
-
-        return result    
-
-#------------------------------------------------------------------------------
-
-class SendPIREP(Request):
-    """A request to send a PIREP to the MAVA website."""
-    _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
-                     const.FLIGHTTYPE_OLDTIMER : "OT",
-                     const.FLIGHTTYPE_VIP : "VIP",
-                     const.FLIGHTTYPE_CHARTER : "CHARTER" }
-
-    _latin2Encoder = codecs.getencoder("iso-8859-2")
-
-    def __init__(self, callback, pirep):
-        """Construct the request for the given PIREP."""
-        super(SendPIREP, self).__init__(callback)
-        self._pirep = pirep
-
-    def run(self):
-        """Perform the sending of the PIREP."""
-        url = "http://www.virtualairlines.hu/malevacars.php"
-
-        pirep = self._pirep
-
-        data = {}
-        data["acarsdata"] = pirep.getACARSText()
-
-        bookedFlight = pirep.bookedFlight
-        data["foglalas_id"] = bookedFlight.id
-        data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
-        data["fltnum"] = bookedFlight.callsign
-        data["depap"] = bookedFlight.departureICAO
-        data["arrap"] = bookedFlight.arrivalICAO
-        data["pass"] = str(bookedFlight.numPassengers)
-        data["crew"] = str(bookedFlight.numCrew)
-        data["cargo"] = str(pirep.cargoWeight)
-        data["bag"] = str(bookedFlight.bagWeight)
-        data["mail"] = str(bookedFlight.mailWeight)
-        
-        data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
-        data["onoff"] = "1" if pirep.online else "0"
-        data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
-        data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
-        data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
-                                                    pirep.blockTimeStart)
-        data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
-        data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
-        data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
-                                                    pirep.flightTimeStart)
-        data["timecomm"] = pirep.getTimeComment()
-        data["fuel"] = "%.0f" % (pirep.fuelUsed,)
-        data["dep_rwy"] = pirep.departureRunway
-        data["arr_rwy"] = pirep.arrivalRunway
-        data["wea_dep"] = pirep.departureMETAR
-        data["wea_arr"] = pirep.arrivalMETAR
-        data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
-        if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
-            data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
-        else:
-            data["mod_alt"] = ""
-        data["sid"] = pirep.sid
-        data["navroute"] = pirep.route
-        data["star"] = pirep.getSTAR()
-        data["aprtype"] = pirep.approachType
-        data["diff"] = "2"
-        data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
-        data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
-        data["kritika"] = pirep.getRatingText()
-        data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
-        data["distance"] = "%.3f" % (pirep.flownDistance,)
-        data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
-
-        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
-        try:
-            result = Result()
-            line = f.readline().strip()
-            print "PIREP result from website:", line
-            result.success = line=="OK"
-            result.alreadyFlown = line=="MARVOLT"
-            result.notAvailable = line=="NOMORE"
-        finally:
-            f.close()
-
-        return result    
-
-#------------------------------------------------------------------------------
-
-class SendACARS(Request):
-    """A request to send an ACARS to the MAVA website."""
-    _latin2Encoder = codecs.getencoder("iso-8859-2")
-
-    def __init__(self, callback, acars):
-        """Construct the request for the given PIREP."""
-        super(SendACARS, self).__init__(callback)
-        self._acars = acars
-
-    def run(self):
-        """Perform the sending of the ACARS."""
-        url = "http://www.virtualairlines.hu/acars2/acarsonline.php"
-
-        acars = self._acars
-        bookedFlight = acars.bookedFlight
-
-        data = {}
-        data["pid"] = acars.pid
-        data["pilot"] = SendACARS._latin2Encoder(acars.pilotName)[0]
-    
-        data["pass"] = str(bookedFlight.numPassengers)
-        data["callsign"] = bookedFlight.callsign
-        data["airplane"] = BookedFlight.TYPE2TYPECODE[bookedFlight.aircraftType]
-        data["from"] = bookedFlight.departureICAO
-        data["to"] = bookedFlight.arrivalICAO        
-        data["lajstrom"] = bookedFlight.tailNumber
-
-        data["block_time"] = acars.getBlockTimeText()
-        data["longitude"] = str(acars.state.longitude)
-        data["latitude"] = str(acars.state.latitude)
-        data["altitude"] = str(acars.state.altitude)
-        data["speed"] = str(acars.state.groundSpeed)
-        
-        data["event"] = acars.getEventText()
-
-        f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
-        try:
-            result = Result()
-        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 sendPIREP(self, callback, pirep):
-        """Send the given PIREP."""
-        self._addRequest(SendPIREP(callback, pirep))
-
-    def sendACARS(self, callback, acars):
-        """Send the given ACARS"""
-        self._addRequest(SendACARS(callback, acars))
-        
-    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)
-    
-
-#------------------------------------------------------------------------------
