Index: src/mlx/acars.py
===================================================================
--- src/mlx/acars.py	(revision 298)
+++ src/mlx/acars.py	(revision 298)
@@ -0,0 +1,51 @@
+
+import const
+
+#------------------------------------------------------------------------------
+
+## @package mlx.acars
+#
+# The handling of the ACARS.
+#
+# This module defines the \ref ACARS class that is used to extract and contain
+# the data needed to send ACARS information to the MAVA server.
+
+#------------------------------------------------------------------------------
+
+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 298)
+++ 	(revision )
@@ -1,51 +1,0 @@
-
-import const
-
-#------------------------------------------------------------------------------
-
-## @package mlx.acars
-#
-# The handling of the ACARS.
-#
-# This module defines the \ref ACARS class that is used to extract and contain
-# the data needed to send ACARS information to the MAVA server.
-
-#------------------------------------------------------------------------------
-
-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 644)
+++ src/mlx/acft.py	(revision 644)
@@ -0,0 +1,1229 @@
+
+import const
+import gates
+import checks
+import fs
+from i18n import xstr
+import util
+
+import sys
+import time
+import traceback
+
+from collections import deque
+
+#---------------------------------------------------------------------------------------
+
+## @package mlx.acft
+#
+# The simulator-independent aircraft classes.
+#
+# This module contains the aircraft classes that contain some general data
+# about each aircraft type in the MAVA Fleet. The base class, \ref Aircraft
+# also implements some parts of the logging and traces some data. The classes
+# are also responsible for creating the \ref mlx.checks "checkers". Most of
+# them are created by the \ref Aircraft class' constructor, but there can be
+# type-specific differences. For example the lights are different for different
+# types, so there is a \ref Aircraft._appendLightsLoggers "function" which can
+# be reimplemented in child classes, if needed. This class maintains also the
+# \ref SmoothedValue "smoothed values" of the IAS and the VS and set these
+# values in the \ref mlx.fs.AircraftState "aircraft state" when it is received
+# from the simulator.
+
+#---------------------------------------------------------------------------------------
+
+# Derate type: no derate possible
+DERATE_NONE = 0
+
+# Derate type: Boeing, i.e. a percentage value.
+# For logging, the percentage value is expected as a string (i.e. whatever the
+# pilot enters into the text field).
+DERATE_BOEING = 1
+
+# Derate type: EPR, i.e. an EPR value.
+# For logging, the EPR value is expected as a string (i.e. whatever the pilot
+# enters into the text field).
+DERATE_EPR = 2
+
+# Derate type: Tupolev, i.e. nominal or takeoff
+# For logging, one of the DERATE_TUPOLEV_xxx values are expected.
+DERATE_TUPOLEV = 3
+
+# Tupolev derate value: nominal
+DERATE_TUPOLEV_NOMINAL = 1
+
+# Tupolev derate value: takeoff
+DERATE_TUPOLEV_TAKEOFF = 2
+
+# Derate type: BAe-146, i.e. enabled or not
+# For logging, a boolean is expected.
+DERATE_B462 = 4
+
+#---------------------------------------------------------------------------------------
+
+class SmoothedValue(object):
+    """A smoothed value."""
+    def __init__(self):
+        """Construct the value."""
+        self._deque = deque()
+        self._sum = 0
+
+    def add(self, length, value):
+        """Add the given value and smooth with the given length."""
+        dequeLength = len(self._deque)
+        while dequeLength>=length:
+            self._sum -= self._deque.popleft()
+            dequeLength -= 1
+
+        self._sum += value
+        self._deque.append(value)
+
+    def get(self):
+        """Get the average."""
+        return self._sum / len(self._deque)
+
+#---------------------------------------------------------------------------------------
+
+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, minLandingFuel = None,
+                 recommendedLandingFuel = None):
+        """Construct the aircraft for the given type."""
+        self._flight = flight
+        self._minLandingFuel = minLandingFuel
+        self._recommendedLandingFuel = recommendedLandingFuel
+
+        self._name = None
+        self._modelName = None
+
+        self._aircraftState = None
+
+        self._maxVS = -10000.0
+        self._minVS = 10000.0
+
+        self._v1r2LineIndex = None
+        self._derateLineID = None
+        self._takeoffAntiIceLineID = None
+        self._vrefLineIndex = None
+        self._landingAntiIceLineID = None
+
+        self.humanWeight = 82.0
+
+        self.initialClimbSpeedAltitude = 1500
+        self.reverseMinSpeed = 50
+
+        self.hasStrobeLight = True
+
+        self.maxTakeOffPitch = 15.0
+        self.maxTouchDownPitch = 15.0
+        self.brakeCoolTime = 10.0
+
+        self._checkers = []
+
+        config = flight.config
+        # Loggers
+
+        self._checkers.append(checks.StageChecker())
+        self._checkers.append(checks.TakeOffLogger())
+
+        self._checkers.append(checks.AltimeterLogger())
+
+        self._ilsLogger = checks.ILSLogger()
+        self._checkers.append(self._ilsLogger)
+        self._nav1Logger = checks.NAV1Logger()
+        self._checkers.append(self._nav1Logger)
+        self._nav2Logger = checks.NAV2Logger()
+        self._checkers.append(self._nav2Logger)
+
+        self._adf1Logger = checks.ADF1Logger()
+        self._checkers.append(self._adf1Logger)
+        self._adf2Logger = checks.ADF2Logger()
+        self._checkers.append(self._adf2Logger)
+
+        self._checkers.append(checks.SquawkLogger())
+
+        self._appendLightsLoggers()
+
+        self._checkers.append(checks.FlapsLogger())
+
+        self._checkers.append(checks.GearsLogger())
+        self._checkers.append(checks.InitialClimbSpeedLogger())
+        self._checkers.append(checks.CruiseSpeedLogger())
+        self._checkers.append(checks.SpoilerLogger())
+        self._checkers.append(checks.APLogger())
+
+        if 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 config.onlineACARS and flight.loggedIn and not flight.entranceExam:
+            self._checkers.append(checks.ACARSSender(flight._gui))
+
+        # Fault checkers
+
+        self._appendLightsCheckers()
+
+        self._checkers.append(checks.TransponderChecker())
+
+        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())
+
+        timeout = 30.0 + config.realIASSmoothingLength - 1
+        self._checkers.append(checks.OverspeedChecker(faultTimeout = timeout))
+
+        self._checkers.append(checks.StallChecker())
+
+        self._checkers.append(checks.PitotChecker())
+
+        self._checkers.append(checks.ReverserLogger())
+        self._checkers.append(checks.ReverserChecker())
+
+        if flight.aircraftType is not None and config.enableApproachCallouts:
+            approachCallouts = flight.config.getApproachCallouts(flight.aircraftType)
+            if approachCallouts:
+                self._checkers.append(checks.ApproachCalloutsPlayer(approachCallouts))
+
+        self._smoothedIAS = SmoothedValue()
+        self._smoothedVS = SmoothedValue()
+
+    @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 minLandingFuel(self):
+        """Get the minimum acceptable amount of the landing fuel."""
+        return self._minLandingFuel
+
+    @property
+    def recommendedLandingFuel(self):
+        """Get the recommended amount of the landing fuel."""
+        return self._recommendedLandingFuel
+
+    @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
+
+    @property
+    def speedInKnots(self):
+        """Indicate if the speed is in knots.
+
+        This default implementation returns True."""
+        return True
+
+    @property
+    def aglInFeet(self):
+        """Indicate if AGL altitudes are to be logged in feet.
+
+        This default implementation returns True."""
+        return True
+
+    @property
+    def timestamp(self):
+        """Get the timestamp of the current state."""
+        return None if self._aircraftState is None \
+               else self._aircraftState.timestamp
+
+    @property
+    def derateType(self):
+        """Get the derate type for this aircraft.
+
+        This default implementation returns DERATE_NONE."""
+        return DERATE_NONE
+
+    def getDerateLine(self, value):
+        """Get the log line for the given derate value.
+
+        It uses the the derate type and produces the standard message for
+        each. This children need not override it, although they can."""
+        dt = self.derateType
+
+        if dt==DERATE_BOEING:
+            return "Derate calculated by the pilot: %s %%" % \
+              ("-" if value is None else value,)
+        elif dt==DERATE_EPR:
+            return "EPR calculated by the pilot: %s" % \
+              ("-" if value is None else value,)
+        elif dt==DERATE_TUPOLEV:
+            return "Thrust setting calculated by the pilot: %s" % \
+              ("-" if value is None else
+               "nominal" if value==DERATE_TUPOLEV_NOMINAL else "takeoff",)
+        elif dt==DERATE_B462:
+            return "Derate setting: %s" % \
+              ("-" if value is None else "enabled" if value else "disabled",)
+        elif dt!=DERATE_NONE:
+            print "mlx.acft.getDerateLine: invalid derate type: " + dt
+
+        return None
+
+    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._name = aircraftName
+        self._modelName = modelName
+        if self._flight.stage is not None:
+            self._logNameAndModel(timestamp)
+
+    def handleState(self, aircraftState):
+        """Called when the state of the aircraft changes.
+
+        This is the function that the simulator calls directly with the new
+        state."""
+        try:
+            config = self._flight.config
+
+            self._smoothedIAS.add(config.realIASSmoothingLength, aircraftState.ias)
+            aircraftState.smoothedIAS = self._smoothedIAS.get()
+
+            self._smoothedVS.add(config.realVSSmoothingLength, aircraftState.vs)
+            aircraftState.smoothedVS = self._smoothedVS.get()
+
+            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)
+        except:
+            print >> sys.stderr, "Failed to handle the state"
+            traceback.print_exc()
+        finally:
+            self._aircraftState = aircraftState
+
+    def setStage(self, aircraftState, newStage):
+        """Set the given stage as the new one and do whatever should be
+        done."""
+        if newStage==const.STAGE_BOARDING:
+            self._logNameAndModel(aircraftState.timestamp)
+
+        oldStage = self._flight.stage
+
+        if self._flight.setStage(aircraftState.timestamp, newStage):
+            if newStage==const.STAGE_PUSHANDTAXI:
+                self.logger.message(aircraftState.timestamp, "Block time start")
+                self._flight.logFuel(aircraftState)
+                self.logger.message(aircraftState.timestamp,
+                                    "ZFW: %.2f 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:
+                if oldStage == const.STAGE_RTO and self._flight.hasRTO:
+                    rtoState = self._flight.rtoState
+                    if (aircraftState.timestamp - rtoState.timestamp) < \
+                       (self.brakeCoolTime * 60.0):
+                        self.logger.fault("brakeCoolTime",
+                                          aircraftState.timestamp,
+                                          "Did not cool the brakes for at least %.f minutes after the RTO" % (self.brakeCoolTime,),
+                                          15.0)
+                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._logQNH(aircraftState)
+                self.logger.message(aircraftState.timestamp,
+                                    "Wind %03.0f/%.0f" % \
+                                    (aircraftState.windDirection,
+                                     aircraftState.windSpeed))
+                self._logRadios(aircraftState)
+                self._logV1R2(aircraftState)
+                self._logDerate(aircraftState)
+                self._logTakeoffAntiIce(aircraftState)
+            elif newStage==const.STAGE_DESCENT or newStage==const.STAGE_LANDING:
+                self._logRadios(aircraftState)
+                if newStage==const.STAGE_LANDING:
+                    self._logQNH(aircraftState)
+            elif newStage==const.STAGE_GOAROUND:
+                from logger import Logger
+                self._flight.handleFault("goaround",
+                                         aircraftState.timestamp,
+                                         "Go-around detected, please, explain!",
+                                         Logger.NO_SCORE)
+            elif newStage==const.STAGE_TAXIAFTERLAND:
+                flight = self._flight
+                bookedFlight = flight.bookedFlight
+                config = flight.config
+                if config.onlineGateSystem and \
+                   flight.loggedIn and \
+                   not flight.entranceExam 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._flight.logFuel(aircraftState)
+                if self._minLandingFuel is not None and \
+                   aircraftState.totalFuel<self._minLandingFuel:
+                    self._flight.handleFault(self.__class__,
+                                             aircraftState.timestamp,
+                                             "The amount of the landing fuel is less than the minimum for this type: %ukgs (possible NO GO!)" %
+                                             (self._minLandingFuel,), 0)
+                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, "Block time end")
+                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/%.0f" % \
+                            (windDirection, windSpeed))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Visibility: %.0f metres" % (visibility,))
+        self._logQNH(self._aircraftState)
+        self._logVRef()
+        self._logLandingAntiIce(self._aircraftState)
+        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,
+                                                                 tdRate)
+        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",))
+        flight = self._flight
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown speed: %.0f %s" % \
+                            (flight.speedFromKnots(ias),
+                             flight.getEnglishSpeedUnit()))
+        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,))
+        self.logger.message(self._aircraftState.timestamp,
+                           "CG: %.1f%%" % \
+                            (self._aircraftState.cog*100.0,))
+
+        if abs(pitch)>self.maxTouchDownPitch:
+            self._flight.handleNoGo("TDPitch", self._aircraftState.timestamp,
+                                    "Touchdown pitch higher than aircraft maximum (%.2f)" % \
+                                    (self.maxTouchDownPitch,),
+                                    "TD TAILSTRIKE NO GO")
+
+    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 updateDerate(self):
+        """Update the derate value from the flight, if the these values
+        have already been logged."""
+        if self._derateLineID is not None:
+            self._logDerate()
+
+    def updateTakeoffAntiIce(self):
+        """Update the take-off anti-ice setting."""
+        if self._takeoffAntiIceLineID is not None:
+            self._logTakeoffAntiIce()
+
+    def _appendLightsLoggers(self):
+        """Append the loggers needed for the lights.
+
+        This default implementation adds the loggers for the anti-collision
+        lights, the landing lights, the strobe lights and the NAV lights."""
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.StrobeLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+    def _appendLightsCheckers(self):
+        """Append the checkers needed for the lights.
+
+        This default implementation adds the checkers for the anti-collision
+        lights, the landing lights, the strobe lights and the NAV lights."""
+        self._checkers.append(checks.AntiCollisionLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+        self._checkers.append(checks.StrobeLightsChecker())
+
+    def _speedToLog(self, speed):
+        """Convert the given speed (being either None or expressed in the
+        flight's speed unit into a string."""
+        if speed is None:
+            return "-"
+        else:
+            return str(speed) + " " + self._flight.getEnglishSpeedUnit()
+
+    def _logV1R2(self, state = None):
+        """Log the V1, Vr and V2 value either newly, or by updating the
+        corresponding line."""
+        message = "Calc. TO speeds: V1: %s, VR: %s, V2: %s" % \
+                  (self._speedToLog(self._flight.v1),
+                   self._speedToLog(self._flight.vr),
+                   self._speedToLog(self._flight.v2))
+
+        if self._v1r2LineIndex is None:
+            if state is None:
+                state = self._aircraftState
+            self._v1r2LineIndex = \
+                self.logger.message(state.timestamp, message)
+        else:
+            self.logger.updateLine(self._v1r2LineIndex, message)
+
+    def _logDerate(self, state = None):
+        """Log the derate values either newly or by updating the corresponding
+        line."""
+        dt = self.derateType
+        if dt==DERATE_NONE:
+            return
+
+        message = self.getDerateLine(self._flight.derate)
+        if message is not None:
+            if self._derateLineID is None:
+                if state is None:
+                    state = self._aircraftState
+                self._derateLineID = \
+                  self.logger.message(state.timestamp, message)
+            else:
+                self.logger.updateLine(self._derateLineID, message)
+
+    def _logTakeoffAntiIce(self, state = None):
+        """Log the take-off anti-ice setting either newly or by updating the
+        corresponding line."""
+        antiIceOn = self._flight.takeoffAntiIceOn
+        if state is not None:
+            antiIceOn = antiIceOn or state.antiIceOn is True
+            self._flight.takeoffAntiIceOn = antiIceOn
+
+        message = "Anti-ice was turned %s" % \
+                  ("ON" if antiIceOn else "OFF")
+
+        if self._takeoffAntiIceLineID is None:
+            if state is None:
+                state = self._aircraftState
+            self._takeoffAntiIceLineID = \
+                self.logger.message(state.timestamp, message)
+        else:
+            self.logger.updateLine(self._takeoffAntiIceLineID, 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" % \
+                  (self._speedToLog(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 updateLandingAntiIce(self):
+        """Update the landing anti-ice setting."""
+        if self._landingAntiIceLineID is not None:
+            self._logLandingAntiIce()
+
+    def _logLandingAntiIce(self, state = None):
+        """Log the landing anti-ice setting either newly or by updating the
+        corresponding line."""
+        antiIceOn = self._flight.landingAntiIceOn
+        if state is not None:
+            antiIceOn = antiIceOn or state.antiIceOn is True
+            self._flight.landingAntiIceOn = antiIceOn
+
+        message = "Anti-ice was turned %s" % \
+                  ("ON" if antiIceOn else "OFF")
+
+        if self._landingAntiIceLineID is None:
+            if state is None:
+                state = self._aircraftState
+            self._landingAntiIceLineID = \
+                self.logger.message(state.timestamp, message)
+        else:
+            self.logger.updateLine(self._landingAntiIceLineID, message)
+
+    def _fleetRetrieved(self, fleet):
+        """Callback for the fleet retrieval result."""
+        if fleet is not None:
+            gateList = ""
+            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
+            for gate in gates.lhbpGates.gates:
+                if gate.isAvailable(gates.lhbpGates, occupiedGateNumbers):
+                    if gateList: gateList += ", "
+                    gateList += gate.number
+            fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
+                           "Free gates: " + gateList, 20)
+
+
+    def _logRadios(self, aircraftState):
+        """Log the radios from the given aircraft state."""
+        flight = self._flight
+        logger = flight.logger
+
+        self._ilsLogger.forceLog(flight, logger, aircraftState)
+        self._nav1Logger.forceLog(flight, logger, aircraftState)
+        self._nav2Logger.forceLog(flight, logger, aircraftState)
+        self._adf1Logger.forceLog(flight, logger, aircraftState)
+        self._adf2Logger.forceLog(flight, logger, aircraftState)
+
+    def _logQNH(self, aircraftState):
+        """Log the current QNH along with the altimeter setting."""
+        self.logger.message(aircraftState.timestamp,
+                            "QNH: %.2f hPa, altimeter: %.2f hPa" % \
+                            (aircraftState.qnh, aircraftState.altimeter))
+
+    def _logNameAndModel(self, timestamp):
+        """Log the aircraft's name and model with taking the timestamp from the
+        given state."""
+        self._flight.logger.message(timestamp,
+                                    "Aircraft: name='%s', model='%s'" % \
+                                    (self._name, self._modelName))
+
+#---------------------------------------------------------------------------------------
+
+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"""
+    def __init__(self, flight, minLandingFuel = 2500,
+                 recommendedLandingFuel = 3500):
+        super(Boeing737, self).__init__(flight,
+                                        minLandingFuel = minLandingFuel,
+                                        recommendedLandingFuel =
+                                        recommendedLandingFuel)
+
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 260,
+                                 2 : 260,
+                                 5 : 250,
+                                 10 : 210,
+                                 15 : 200,
+                                 25 : 190,
+                                 30 : 175,
+                                 40 : 162 }
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_BOEING
+
+#---------------------------------------------------------------------------------------
+
+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
+        self.maxTakeOffPitch = 16.2
+        self.maxTouchDownPitch = 14.7
+
+#---------------------------------------------------------------------------------------
+
+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
+        self.maxTakeOffPitch = 14.7
+        self.maxTouchDownPitch = 13.2
+
+#---------------------------------------------------------------------------------------
+
+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
+        self.maxTakeOffPitch = 11
+        self.maxTouchDownPitch = 9.5
+
+#---------------------------------------------------------------------------------------
+
+class B738Charter(B738):
+    """Boeing 737-800 aircraft used for charters."""
+    def __init__(self, flight):
+        super(B738Charter, self).__init__(flight)
+        self.mtow = 77791
+
+#---------------------------------------------------------------------------------------
+
+class Boeing737CL(Boeing737):
+    """Base class for the various aircraft in the Boeing 737 Classic family."""
+    def __init__(self, flight):
+        super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
+                                          recommendedLandingFuel = None)
+
+#---------------------------------------------------------------------------------------
+
+class B733(Boeing737CL):
+    """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
+        self.maxTakeOffPitch = 13.4
+        self.maxTouchDownPitch = 12.0
+
+#---------------------------------------------------------------------------------------
+
+class B734(Boeing737CL):
+    """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
+        self.maxTakeOffPitch = 11.4
+        self.maxTouchDownPitch = 10
+
+#---------------------------------------------------------------------------------------
+
+class B735(Boeing737CL):
+    """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
+        self.maxTakeOffPitch = 14.7
+        self.maxTouchDownPitch = 13.2
+
+#---------------------------------------------------------------------------------------
+
+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."""
+
+    def __init__(self, flight):
+        super(DH8D, self).__init__(flight, minLandingFuel = 2000)
+        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 }
+        self.maxTakeOffPitch = 8.0
+        self.maxTouchDownPitch = 7.0
+
+#---------------------------------------------------------------------------------------
+
+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"""
+
+    def __init__(self, flight, minLandingFuel = 7000):
+        super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 255,
+                                 5 : 235,
+                                 10 : 215,
+                                 20 : 215,
+                                 25 : 185,
+                                 30 : 175 }
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_BOEING
+
+#---------------------------------------------------------------------------------------
+
+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
+        self.maxTakeOffPitch = 13.1
+        self.maxTouchDownPitch = 11.6
+
+#---------------------------------------------------------------------------------------
+
+class B763(Boeing767):
+    """Boeing 767-300 aircraft."""
+    def __init__(self, flight):
+        super(B763, self).__init__(flight)
+        self.dow = 91311
+        self.mtow = 181436
+        self.mlw = 137892
+        self.mzfw = 130635
+        self.maxTakeOffPitch = 9.6
+        self.maxTouchDownPitch = 8.1
+
+#---------------------------------------------------------------------------------------
+
+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."""
+    def __init__(self, flight):
+        super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
+        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 }
+        self.maxTakeOffPitch = 18.0
+        self.maxTouchDownPitch = 18.0
+
+#---------------------------------------------------------------------------------------
+
+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."""
+    def __init__(self, flight):
+        super(F70, self).__init__(flight, minLandingFuel = 1900)
+        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 }
+        self.reverseMinSpeed = 50
+        self.maxTakeOffPitch = 16.0
+        self.maxTouchDownPitch = 16.0
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_EPR
+
+#---------------------------------------------------------------------------------------
+
+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."""
+    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
+
+#---------------------------------------------------------------------------------------
+
+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."""
+    def __init__(self, flight):
+        super(T134, self).__init__(flight, minLandingFuel = 3000)
+        self.dow = 29500
+        self.mtow = 49000
+        self.mlw = 43000
+        self.mzfw = 38500
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 10 : 450,
+                                 20 : 400,
+                                 30 : 300 }
+        self.reverseMinSpeed = 50
+
+        self.hasStrobeLight = False
+
+        self.maxTakeOffPitch = 16.0
+        self.maxTouchDownPitch = 16.0
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_TUPOLEV
+
+    @property
+    def speedInKnots(self):
+        """Indicate if the speed is in knots."""
+        return False
+
+    @property
+    def aglInFeet(self):
+        """Indicate if AGL altituedes are in feet."""
+        return False
+
+    def _appendLightsLoggers(self):
+        """Append the loggers needed for the lights."""
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+    def _appendLightsCheckers(self):
+        """Append the checkers needed for the lights."""
+        self._checkers.append(checks.TupolevAntiCollisionLightsChecker())
+        self._checkers.append(checks.TupolevLandingLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+
+#---------------------------------------------------------------------------------------
+
+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"""
+    def __init__(self, flight):
+        super(T154, self).__init__(flight, minLandingFuel = 5000)
+        self.dow = 53259
+        self.mtow = 98000
+        self.mlw = 78000
+        self.mzfw = 72000
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 15 : 227,
+                                 28 : 194,
+                                 45 : 162 }
+        self.reverseMinSpeed = 50
+
+        self.hasStrobeLight = False
+
+        self.maxTakeOffPitch = 16.0
+        self.maxTouchDownPitch = 16.0
+
+    @property
+    def speedInKnots(self):
+        """Indicate if the speed is in knots."""
+        return False
+
+    @property
+    def aglInFeet(self):
+        """Indicate if AGL altituedes are in feet."""
+        return False
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_TUPOLEV
+
+    def _appendLightsLoggers(self):
+        """Append the loggers needed for the lights."""
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+    def _appendLightsCheckers(self):
+        """Append the checkers needed for the lights."""
+        self._checkers.append(checks.AntiCollisionLightsChecker())
+        self._checkers.append(checks.TupolevLandingLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+
+#---------------------------------------------------------------------------------------
+
+class YK40(Aircraft):
+    """Yakovlev Yak-40 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(YK40, self).__init__(flight)
+        self.dow = 9400
+        self.mtow = 17200
+        self.mlw = 16800
+        self.mzfw = 12100
+        self.gearSpeedLimit = 165
+        self.flapSpeedLimits = { 20 : 165,
+                                 35 : 135 }
+
+        self.hasStrobeLight = False
+
+    @property
+    def speedInKnots(self):
+        """Indicate if the speed is in knots."""
+        return False
+
+    @property
+    def aglInFeet(self):
+        """Indicate if AGL altituedes are in feet."""
+        return False
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_TUPOLEV
+
+    def _appendLightsLoggers(self):
+        """Append the loggers needed for the lights."""
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+    def _appendLightsCheckers(self):
+        """Append the checkers needed for the lights."""
+        self._checkers.append(checks.AntiCollisionLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+
+#---------------------------------------------------------------------------------------
+
+class B462(Aircraft):
+    """British Aerospace BAe-146 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, centre, right
+    - n1: left outer, left inner, right inner, right outer
+    - reverser: empty (the plane has no reversers)"""
+    def __init__(self, flight):
+        super(B462, self).__init__(flight)
+        self.dow = 25706
+        self.mtow = 43998
+        self.mlw = 38599
+        self.mzfw = 33792
+        self.gearSpeedLimit = 210
+        self.flapSpeedLimits = { 18 : 217,
+                                 24 : 180,
+                                 30 : 170,
+                                 33 : 150 }
+
+    @property
+    def derateType(self):
+        """Get the derate type for this type."""
+        return DERATE_B462
+
+#---------------------------------------------------------------------------------------
+
+mostFuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
+                 const.FUELTANK_LEFT_AUX,
+                 const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT_AUX,
+                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
+
+#---------------------------------------------------------------------------------------
+
+_classes = { const.AIRCRAFT_B736  : B736,
+             const.AIRCRAFT_B737  : B737,
+             const.AIRCRAFT_B738  : B738,
+             const.AIRCRAFT_B738C : B738Charter,
+             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,
+             const.AIRCRAFT_B462  : B462 }
+
+#---------------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    value = SmoothedValue()
+
+    print "Adding 1, 12.0"
+    value.add(1, 12.0)
+    print value.get()
+
+    print "Adding 1, 15.0"
+    value.add(1, 15.0)
+    print value.get()
+
+    print "Adding 2, 18.0"
+    value.add(2, 18.0)
+    print value.get()
+
+    print "Adding 2, 20.0"
+    value.add(2, 20.0)
+    print value.get()
+
+    print "Adding 5, 22.0"
+    value.add(5, 22.0)
+    print value.get()
+
+    print "Adding 5, 25.0"
+    value.add(5, 25.0)
+    print value.get()
+
+    print "Adding 5, 29.0"
+    value.add(5, 29.0)
+    print value.get()
+
+    print "Adding 5, 21.0"
+    value.add(5, 21.0)
+    print value.get()
+
+    print "Adding 5, 26.0"
+    value.add(5, 26.0)
+    print value.get()
+
+    print "Adding 2, 30.0"
+    value.add(2, 30.0)
+    print value.get()
+
+    print "Adding 2, 55.0"
+    value.add(2, 55.0)
+    print value.get()
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/acft.py
===================================================================
--- src/mlx/acft.py	(revision 644)
+++ 	(revision )
@@ -1,1229 +1,0 @@
-
-import const
-import gates
-import checks
-import fs
-from i18n import xstr
-import util
-
-import sys
-import time
-import traceback
-
-from collections import deque
-
-#---------------------------------------------------------------------------------------
-
-## @package mlx.acft
-#
-# The simulator-independent aircraft classes.
-#
-# This module contains the aircraft classes that contain some general data
-# about each aircraft type in the MAVA Fleet. The base class, \ref Aircraft
-# also implements some parts of the logging and traces some data. The classes
-# are also responsible for creating the \ref mlx.checks "checkers". Most of
-# them are created by the \ref Aircraft class' constructor, but there can be
-# type-specific differences. For example the lights are different for different
-# types, so there is a \ref Aircraft._appendLightsLoggers "function" which can
-# be reimplemented in child classes, if needed. This class maintains also the
-# \ref SmoothedValue "smoothed values" of the IAS and the VS and set these
-# values in the \ref mlx.fs.AircraftState "aircraft state" when it is received
-# from the simulator.
-
-#---------------------------------------------------------------------------------------
-
-# Derate type: no derate possible
-DERATE_NONE = 0
-
-# Derate type: Boeing, i.e. a percentage value.
-# For logging, the percentage value is expected as a string (i.e. whatever the
-# pilot enters into the text field).
-DERATE_BOEING = 1
-
-# Derate type: EPR, i.e. an EPR value.
-# For logging, the EPR value is expected as a string (i.e. whatever the pilot
-# enters into the text field).
-DERATE_EPR = 2
-
-# Derate type: Tupolev, i.e. nominal or takeoff
-# For logging, one of the DERATE_TUPOLEV_xxx values are expected.
-DERATE_TUPOLEV = 3
-
-# Tupolev derate value: nominal
-DERATE_TUPOLEV_NOMINAL = 1
-
-# Tupolev derate value: takeoff
-DERATE_TUPOLEV_TAKEOFF = 2
-
-# Derate type: BAe-146, i.e. enabled or not
-# For logging, a boolean is expected.
-DERATE_B462 = 4
-
-#---------------------------------------------------------------------------------------
-
-class SmoothedValue(object):
-    """A smoothed value."""
-    def __init__(self):
-        """Construct the value."""
-        self._deque = deque()
-        self._sum = 0
-
-    def add(self, length, value):
-        """Add the given value and smooth with the given length."""
-        dequeLength = len(self._deque)
-        while dequeLength>=length:
-            self._sum -= self._deque.popleft()
-            dequeLength -= 1
-
-        self._sum += value
-        self._deque.append(value)
-
-    def get(self):
-        """Get the average."""
-        return self._sum / len(self._deque)
-
-#---------------------------------------------------------------------------------------
-
-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, minLandingFuel = None,
-                 recommendedLandingFuel = None):
-        """Construct the aircraft for the given type."""
-        self._flight = flight
-        self._minLandingFuel = minLandingFuel
-        self._recommendedLandingFuel = recommendedLandingFuel
-
-        self._name = None
-        self._modelName = None
-
-        self._aircraftState = None
-
-        self._maxVS = -10000.0
-        self._minVS = 10000.0
-
-        self._v1r2LineIndex = None
-        self._derateLineID = None
-        self._takeoffAntiIceLineID = None
-        self._vrefLineIndex = None
-        self._landingAntiIceLineID = None
-
-        self.humanWeight = 82.0
-
-        self.initialClimbSpeedAltitude = 1500
-        self.reverseMinSpeed = 50
-
-        self.hasStrobeLight = True
-
-        self.maxTakeOffPitch = 15.0
-        self.maxTouchDownPitch = 15.0
-        self.brakeCoolTime = 10.0
-
-        self._checkers = []
-
-        config = flight.config
-        # Loggers
-
-        self._checkers.append(checks.StageChecker())
-        self._checkers.append(checks.TakeOffLogger())
-
-        self._checkers.append(checks.AltimeterLogger())
-
-        self._ilsLogger = checks.ILSLogger()
-        self._checkers.append(self._ilsLogger)
-        self._nav1Logger = checks.NAV1Logger()
-        self._checkers.append(self._nav1Logger)
-        self._nav2Logger = checks.NAV2Logger()
-        self._checkers.append(self._nav2Logger)
-
-        self._adf1Logger = checks.ADF1Logger()
-        self._checkers.append(self._adf1Logger)
-        self._adf2Logger = checks.ADF2Logger()
-        self._checkers.append(self._adf2Logger)
-
-        self._checkers.append(checks.SquawkLogger())
-
-        self._appendLightsLoggers()
-
-        self._checkers.append(checks.FlapsLogger())
-
-        self._checkers.append(checks.GearsLogger())
-        self._checkers.append(checks.InitialClimbSpeedLogger())
-        self._checkers.append(checks.CruiseSpeedLogger())
-        self._checkers.append(checks.SpoilerLogger())
-        self._checkers.append(checks.APLogger())
-
-        if 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 config.onlineACARS and flight.loggedIn and not flight.entranceExam:
-            self._checkers.append(checks.ACARSSender(flight._gui))
-
-        # Fault checkers
-
-        self._appendLightsCheckers()
-
-        self._checkers.append(checks.TransponderChecker())
-
-        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())
-
-        timeout = 30.0 + config.realIASSmoothingLength - 1
-        self._checkers.append(checks.OverspeedChecker(faultTimeout = timeout))
-
-        self._checkers.append(checks.StallChecker())
-
-        self._checkers.append(checks.PitotChecker())
-
-        self._checkers.append(checks.ReverserLogger())
-        self._checkers.append(checks.ReverserChecker())
-
-        if flight.aircraftType is not None and config.enableApproachCallouts:
-            approachCallouts = flight.config.getApproachCallouts(flight.aircraftType)
-            if approachCallouts:
-                self._checkers.append(checks.ApproachCalloutsPlayer(approachCallouts))
-
-        self._smoothedIAS = SmoothedValue()
-        self._smoothedVS = SmoothedValue()
-
-    @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 minLandingFuel(self):
-        """Get the minimum acceptable amount of the landing fuel."""
-        return self._minLandingFuel
-
-    @property
-    def recommendedLandingFuel(self):
-        """Get the recommended amount of the landing fuel."""
-        return self._recommendedLandingFuel
-
-    @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
-
-    @property
-    def speedInKnots(self):
-        """Indicate if the speed is in knots.
-
-        This default implementation returns True."""
-        return True
-
-    @property
-    def aglInFeet(self):
-        """Indicate if AGL altitudes are to be logged in feet.
-
-        This default implementation returns True."""
-        return True
-
-    @property
-    def timestamp(self):
-        """Get the timestamp of the current state."""
-        return None if self._aircraftState is None \
-               else self._aircraftState.timestamp
-
-    @property
-    def derateType(self):
-        """Get the derate type for this aircraft.
-
-        This default implementation returns DERATE_NONE."""
-        return DERATE_NONE
-
-    def getDerateLine(self, value):
-        """Get the log line for the given derate value.
-
-        It uses the the derate type and produces the standard message for
-        each. This children need not override it, although they can."""
-        dt = self.derateType
-
-        if dt==DERATE_BOEING:
-            return "Derate calculated by the pilot: %s %%" % \
-              ("-" if value is None else value,)
-        elif dt==DERATE_EPR:
-            return "EPR calculated by the pilot: %s" % \
-              ("-" if value is None else value,)
-        elif dt==DERATE_TUPOLEV:
-            return "Thrust setting calculated by the pilot: %s" % \
-              ("-" if value is None else
-               "nominal" if value==DERATE_TUPOLEV_NOMINAL else "takeoff",)
-        elif dt==DERATE_B462:
-            return "Derate setting: %s" % \
-              ("-" if value is None else "enabled" if value else "disabled",)
-        elif dt!=DERATE_NONE:
-            print "mlx.acft.getDerateLine: invalid derate type: " + dt
-
-        return None
-
-    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._name = aircraftName
-        self._modelName = modelName
-        if self._flight.stage is not None:
-            self._logNameAndModel(timestamp)
-
-    def handleState(self, aircraftState):
-        """Called when the state of the aircraft changes.
-
-        This is the function that the simulator calls directly with the new
-        state."""
-        try:
-            config = self._flight.config
-
-            self._smoothedIAS.add(config.realIASSmoothingLength, aircraftState.ias)
-            aircraftState.smoothedIAS = self._smoothedIAS.get()
-
-            self._smoothedVS.add(config.realVSSmoothingLength, aircraftState.vs)
-            aircraftState.smoothedVS = self._smoothedVS.get()
-
-            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)
-        except:
-            print >> sys.stderr, "Failed to handle the state"
-            traceback.print_exc()
-        finally:
-            self._aircraftState = aircraftState
-
-    def setStage(self, aircraftState, newStage):
-        """Set the given stage as the new one and do whatever should be
-        done."""
-        if newStage==const.STAGE_BOARDING:
-            self._logNameAndModel(aircraftState.timestamp)
-
-        oldStage = self._flight.stage
-
-        if self._flight.setStage(aircraftState.timestamp, newStage):
-            if newStage==const.STAGE_PUSHANDTAXI:
-                self.logger.message(aircraftState.timestamp, "Block time start")
-                self._flight.logFuel(aircraftState)
-                self.logger.message(aircraftState.timestamp,
-                                    "ZFW: %.2f 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:
-                if oldStage == const.STAGE_RTO and self._flight.hasRTO:
-                    rtoState = self._flight.rtoState
-                    if (aircraftState.timestamp - rtoState.timestamp) < \
-                       (self.brakeCoolTime * 60.0):
-                        self.logger.fault("brakeCoolTime",
-                                          aircraftState.timestamp,
-                                          "Did not cool the brakes for at least %.f minutes after the RTO" % (self.brakeCoolTime,),
-                                          15.0)
-                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._logQNH(aircraftState)
-                self.logger.message(aircraftState.timestamp,
-                                    "Wind %03.0f/%.0f" % \
-                                    (aircraftState.windDirection,
-                                     aircraftState.windSpeed))
-                self._logRadios(aircraftState)
-                self._logV1R2(aircraftState)
-                self._logDerate(aircraftState)
-                self._logTakeoffAntiIce(aircraftState)
-            elif newStage==const.STAGE_DESCENT or newStage==const.STAGE_LANDING:
-                self._logRadios(aircraftState)
-                if newStage==const.STAGE_LANDING:
-                    self._logQNH(aircraftState)
-            elif newStage==const.STAGE_GOAROUND:
-                from logger import Logger
-                self._flight.handleFault("goaround",
-                                         aircraftState.timestamp,
-                                         "Go-around detected, please, explain!",
-                                         Logger.NO_SCORE)
-            elif newStage==const.STAGE_TAXIAFTERLAND:
-                flight = self._flight
-                bookedFlight = flight.bookedFlight
-                config = flight.config
-                if config.onlineGateSystem and \
-                   flight.loggedIn and \
-                   not flight.entranceExam 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._flight.logFuel(aircraftState)
-                if self._minLandingFuel is not None and \
-                   aircraftState.totalFuel<self._minLandingFuel:
-                    self._flight.handleFault(self.__class__,
-                                             aircraftState.timestamp,
-                                             "The amount of the landing fuel is less than the minimum for this type: %ukgs (possible NO GO!)" %
-                                             (self._minLandingFuel,), 0)
-                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, "Block time end")
-                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/%.0f" % \
-                            (windDirection, windSpeed))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Visibility: %.0f metres" % (visibility,))
-        self._logQNH(self._aircraftState)
-        self._logVRef()
-        self._logLandingAntiIce(self._aircraftState)
-        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,
-                                                                 tdRate)
-        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",))
-        flight = self._flight
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown speed: %.0f %s" % \
-                            (flight.speedFromKnots(ias),
-                             flight.getEnglishSpeedUnit()))
-        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,))
-        self.logger.message(self._aircraftState.timestamp,
-                           "CG: %.1f%%" % \
-                            (self._aircraftState.cog*100.0,))
-
-        if abs(pitch)>self.maxTouchDownPitch:
-            self._flight.handleNoGo("TDPitch", self._aircraftState.timestamp,
-                                    "Touchdown pitch higher than aircraft maximum (%.2f)" % \
-                                    (self.maxTouchDownPitch,),
-                                    "TD TAILSTRIKE NO GO")
-
-    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 updateDerate(self):
-        """Update the derate value from the flight, if the these values
-        have already been logged."""
-        if self._derateLineID is not None:
-            self._logDerate()
-
-    def updateTakeoffAntiIce(self):
-        """Update the take-off anti-ice setting."""
-        if self._takeoffAntiIceLineID is not None:
-            self._logTakeoffAntiIce()
-
-    def _appendLightsLoggers(self):
-        """Append the loggers needed for the lights.
-
-        This default implementation adds the loggers for the anti-collision
-        lights, the landing lights, the strobe lights and the NAV lights."""
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.StrobeLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-    def _appendLightsCheckers(self):
-        """Append the checkers needed for the lights.
-
-        This default implementation adds the checkers for the anti-collision
-        lights, the landing lights, the strobe lights and the NAV lights."""
-        self._checkers.append(checks.AntiCollisionLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-        self._checkers.append(checks.StrobeLightsChecker())
-
-    def _speedToLog(self, speed):
-        """Convert the given speed (being either None or expressed in the
-        flight's speed unit into a string."""
-        if speed is None:
-            return "-"
-        else:
-            return str(speed) + " " + self._flight.getEnglishSpeedUnit()
-
-    def _logV1R2(self, state = None):
-        """Log the V1, Vr and V2 value either newly, or by updating the
-        corresponding line."""
-        message = "Calc. TO speeds: V1: %s, VR: %s, V2: %s" % \
-                  (self._speedToLog(self._flight.v1),
-                   self._speedToLog(self._flight.vr),
-                   self._speedToLog(self._flight.v2))
-
-        if self._v1r2LineIndex is None:
-            if state is None:
-                state = self._aircraftState
-            self._v1r2LineIndex = \
-                self.logger.message(state.timestamp, message)
-        else:
-            self.logger.updateLine(self._v1r2LineIndex, message)
-
-    def _logDerate(self, state = None):
-        """Log the derate values either newly or by updating the corresponding
-        line."""
-        dt = self.derateType
-        if dt==DERATE_NONE:
-            return
-
-        message = self.getDerateLine(self._flight.derate)
-        if message is not None:
-            if self._derateLineID is None:
-                if state is None:
-                    state = self._aircraftState
-                self._derateLineID = \
-                  self.logger.message(state.timestamp, message)
-            else:
-                self.logger.updateLine(self._derateLineID, message)
-
-    def _logTakeoffAntiIce(self, state = None):
-        """Log the take-off anti-ice setting either newly or by updating the
-        corresponding line."""
-        antiIceOn = self._flight.takeoffAntiIceOn
-        if state is not None:
-            antiIceOn = antiIceOn or state.antiIceOn is True
-            self._flight.takeoffAntiIceOn = antiIceOn
-
-        message = "Anti-ice was turned %s" % \
-                  ("ON" if antiIceOn else "OFF")
-
-        if self._takeoffAntiIceLineID is None:
-            if state is None:
-                state = self._aircraftState
-            self._takeoffAntiIceLineID = \
-                self.logger.message(state.timestamp, message)
-        else:
-            self.logger.updateLine(self._takeoffAntiIceLineID, 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" % \
-                  (self._speedToLog(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 updateLandingAntiIce(self):
-        """Update the landing anti-ice setting."""
-        if self._landingAntiIceLineID is not None:
-            self._logLandingAntiIce()
-
-    def _logLandingAntiIce(self, state = None):
-        """Log the landing anti-ice setting either newly or by updating the
-        corresponding line."""
-        antiIceOn = self._flight.landingAntiIceOn
-        if state is not None:
-            antiIceOn = antiIceOn or state.antiIceOn is True
-            self._flight.landingAntiIceOn = antiIceOn
-
-        message = "Anti-ice was turned %s" % \
-                  ("ON" if antiIceOn else "OFF")
-
-        if self._landingAntiIceLineID is None:
-            if state is None:
-                state = self._aircraftState
-            self._landingAntiIceLineID = \
-                self.logger.message(state.timestamp, message)
-        else:
-            self.logger.updateLine(self._landingAntiIceLineID, message)
-
-    def _fleetRetrieved(self, fleet):
-        """Callback for the fleet retrieval result."""
-        if fleet is not None:
-            gateList = ""
-            occupiedGateNumbers = fleet.getOccupiedGateNumbers()
-            for gate in gates.lhbpGates.gates:
-                if gate.isAvailable(gates.lhbpGates, occupiedGateNumbers):
-                    if gateList: gateList += ", "
-                    gateList += gate.number
-            fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
-                           "Free gates: " + gateList, 20)
-
-
-    def _logRadios(self, aircraftState):
-        """Log the radios from the given aircraft state."""
-        flight = self._flight
-        logger = flight.logger
-
-        self._ilsLogger.forceLog(flight, logger, aircraftState)
-        self._nav1Logger.forceLog(flight, logger, aircraftState)
-        self._nav2Logger.forceLog(flight, logger, aircraftState)
-        self._adf1Logger.forceLog(flight, logger, aircraftState)
-        self._adf2Logger.forceLog(flight, logger, aircraftState)
-
-    def _logQNH(self, aircraftState):
-        """Log the current QNH along with the altimeter setting."""
-        self.logger.message(aircraftState.timestamp,
-                            "QNH: %.2f hPa, altimeter: %.2f hPa" % \
-                            (aircraftState.qnh, aircraftState.altimeter))
-
-    def _logNameAndModel(self, timestamp):
-        """Log the aircraft's name and model with taking the timestamp from the
-        given state."""
-        self._flight.logger.message(timestamp,
-                                    "Aircraft: name='%s', model='%s'" % \
-                                    (self._name, self._modelName))
-
-#---------------------------------------------------------------------------------------
-
-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"""
-    def __init__(self, flight, minLandingFuel = 2500,
-                 recommendedLandingFuel = 3500):
-        super(Boeing737, self).__init__(flight,
-                                        minLandingFuel = minLandingFuel,
-                                        recommendedLandingFuel =
-                                        recommendedLandingFuel)
-
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 260,
-                                 2 : 260,
-                                 5 : 250,
-                                 10 : 210,
-                                 15 : 200,
-                                 25 : 190,
-                                 30 : 175,
-                                 40 : 162 }
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_BOEING
-
-#---------------------------------------------------------------------------------------
-
-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
-        self.maxTakeOffPitch = 16.2
-        self.maxTouchDownPitch = 14.7
-
-#---------------------------------------------------------------------------------------
-
-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
-        self.maxTakeOffPitch = 14.7
-        self.maxTouchDownPitch = 13.2
-
-#---------------------------------------------------------------------------------------
-
-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
-        self.maxTakeOffPitch = 11
-        self.maxTouchDownPitch = 9.5
-
-#---------------------------------------------------------------------------------------
-
-class B738Charter(B738):
-    """Boeing 737-800 aircraft used for charters."""
-    def __init__(self, flight):
-        super(B738Charter, self).__init__(flight)
-        self.mtow = 77791
-
-#---------------------------------------------------------------------------------------
-
-class Boeing737CL(Boeing737):
-    """Base class for the various aircraft in the Boeing 737 Classic family."""
-    def __init__(self, flight):
-        super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
-                                          recommendedLandingFuel = None)
-
-#---------------------------------------------------------------------------------------
-
-class B733(Boeing737CL):
-    """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
-        self.maxTakeOffPitch = 13.4
-        self.maxTouchDownPitch = 12.0
-
-#---------------------------------------------------------------------------------------
-
-class B734(Boeing737CL):
-    """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
-        self.maxTakeOffPitch = 11.4
-        self.maxTouchDownPitch = 10
-
-#---------------------------------------------------------------------------------------
-
-class B735(Boeing737CL):
-    """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
-        self.maxTakeOffPitch = 14.7
-        self.maxTouchDownPitch = 13.2
-
-#---------------------------------------------------------------------------------------
-
-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."""
-
-    def __init__(self, flight):
-        super(DH8D, self).__init__(flight, minLandingFuel = 2000)
-        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 }
-        self.maxTakeOffPitch = 8.0
-        self.maxTouchDownPitch = 7.0
-
-#---------------------------------------------------------------------------------------
-
-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"""
-
-    def __init__(self, flight, minLandingFuel = 7000):
-        super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 255,
-                                 5 : 235,
-                                 10 : 215,
-                                 20 : 215,
-                                 25 : 185,
-                                 30 : 175 }
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_BOEING
-
-#---------------------------------------------------------------------------------------
-
-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
-        self.maxTakeOffPitch = 13.1
-        self.maxTouchDownPitch = 11.6
-
-#---------------------------------------------------------------------------------------
-
-class B763(Boeing767):
-    """Boeing 767-300 aircraft."""
-    def __init__(self, flight):
-        super(B763, self).__init__(flight)
-        self.dow = 91311
-        self.mtow = 181436
-        self.mlw = 137892
-        self.mzfw = 130635
-        self.maxTakeOffPitch = 9.6
-        self.maxTouchDownPitch = 8.1
-
-#---------------------------------------------------------------------------------------
-
-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."""
-    def __init__(self, flight):
-        super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
-        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 }
-        self.maxTakeOffPitch = 18.0
-        self.maxTouchDownPitch = 18.0
-
-#---------------------------------------------------------------------------------------
-
-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."""
-    def __init__(self, flight):
-        super(F70, self).__init__(flight, minLandingFuel = 1900)
-        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 }
-        self.reverseMinSpeed = 50
-        self.maxTakeOffPitch = 16.0
-        self.maxTouchDownPitch = 16.0
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_EPR
-
-#---------------------------------------------------------------------------------------
-
-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."""
-    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
-
-#---------------------------------------------------------------------------------------
-
-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."""
-    def __init__(self, flight):
-        super(T134, self).__init__(flight, minLandingFuel = 3000)
-        self.dow = 29500
-        self.mtow = 49000
-        self.mlw = 43000
-        self.mzfw = 38500
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 10 : 450,
-                                 20 : 400,
-                                 30 : 300 }
-        self.reverseMinSpeed = 50
-
-        self.hasStrobeLight = False
-
-        self.maxTakeOffPitch = 16.0
-        self.maxTouchDownPitch = 16.0
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_TUPOLEV
-
-    @property
-    def speedInKnots(self):
-        """Indicate if the speed is in knots."""
-        return False
-
-    @property
-    def aglInFeet(self):
-        """Indicate if AGL altituedes are in feet."""
-        return False
-
-    def _appendLightsLoggers(self):
-        """Append the loggers needed for the lights."""
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-    def _appendLightsCheckers(self):
-        """Append the checkers needed for the lights."""
-        self._checkers.append(checks.TupolevAntiCollisionLightsChecker())
-        self._checkers.append(checks.TupolevLandingLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-
-#---------------------------------------------------------------------------------------
-
-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"""
-    def __init__(self, flight):
-        super(T154, self).__init__(flight, minLandingFuel = 5000)
-        self.dow = 53259
-        self.mtow = 98000
-        self.mlw = 78000
-        self.mzfw = 72000
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 15 : 227,
-                                 28 : 194,
-                                 45 : 162 }
-        self.reverseMinSpeed = 50
-
-        self.hasStrobeLight = False
-
-        self.maxTakeOffPitch = 16.0
-        self.maxTouchDownPitch = 16.0
-
-    @property
-    def speedInKnots(self):
-        """Indicate if the speed is in knots."""
-        return False
-
-    @property
-    def aglInFeet(self):
-        """Indicate if AGL altituedes are in feet."""
-        return False
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_TUPOLEV
-
-    def _appendLightsLoggers(self):
-        """Append the loggers needed for the lights."""
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-    def _appendLightsCheckers(self):
-        """Append the checkers needed for the lights."""
-        self._checkers.append(checks.AntiCollisionLightsChecker())
-        self._checkers.append(checks.TupolevLandingLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-
-#---------------------------------------------------------------------------------------
-
-class YK40(Aircraft):
-    """Yakovlev Yak-40 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(YK40, self).__init__(flight)
-        self.dow = 9400
-        self.mtow = 17200
-        self.mlw = 16800
-        self.mzfw = 12100
-        self.gearSpeedLimit = 165
-        self.flapSpeedLimits = { 20 : 165,
-                                 35 : 135 }
-
-        self.hasStrobeLight = False
-
-    @property
-    def speedInKnots(self):
-        """Indicate if the speed is in knots."""
-        return False
-
-    @property
-    def aglInFeet(self):
-        """Indicate if AGL altituedes are in feet."""
-        return False
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_TUPOLEV
-
-    def _appendLightsLoggers(self):
-        """Append the loggers needed for the lights."""
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-    def _appendLightsCheckers(self):
-        """Append the checkers needed for the lights."""
-        self._checkers.append(checks.AntiCollisionLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-
-#---------------------------------------------------------------------------------------
-
-class B462(Aircraft):
-    """British Aerospace BAe-146 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, centre, right
-    - n1: left outer, left inner, right inner, right outer
-    - reverser: empty (the plane has no reversers)"""
-    def __init__(self, flight):
-        super(B462, self).__init__(flight)
-        self.dow = 25706
-        self.mtow = 43998
-        self.mlw = 38599
-        self.mzfw = 33792
-        self.gearSpeedLimit = 210
-        self.flapSpeedLimits = { 18 : 217,
-                                 24 : 180,
-                                 30 : 170,
-                                 33 : 150 }
-
-    @property
-    def derateType(self):
-        """Get the derate type for this type."""
-        return DERATE_B462
-
-#---------------------------------------------------------------------------------------
-
-mostFuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
-                 const.FUELTANK_LEFT_AUX,
-                 const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT_AUX,
-                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
-
-#---------------------------------------------------------------------------------------
-
-_classes = { const.AIRCRAFT_B736  : B736,
-             const.AIRCRAFT_B737  : B737,
-             const.AIRCRAFT_B738  : B738,
-             const.AIRCRAFT_B738C : B738Charter,
-             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,
-             const.AIRCRAFT_B462  : B462 }
-
-#---------------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    value = SmoothedValue()
-
-    print "Adding 1, 12.0"
-    value.add(1, 12.0)
-    print value.get()
-
-    print "Adding 1, 15.0"
-    value.add(1, 15.0)
-    print value.get()
-
-    print "Adding 2, 18.0"
-    value.add(2, 18.0)
-    print value.get()
-
-    print "Adding 2, 20.0"
-    value.add(2, 20.0)
-    print value.get()
-
-    print "Adding 5, 22.0"
-    value.add(5, 22.0)
-    print value.get()
-
-    print "Adding 5, 25.0"
-    value.add(5, 25.0)
-    print value.get()
-
-    print "Adding 5, 29.0"
-    value.add(5, 29.0)
-    print value.get()
-
-    print "Adding 5, 21.0"
-    value.add(5, 21.0)
-    print value.get()
-
-    print "Adding 5, 26.0"
-    value.add(5, 26.0)
-    print value.get()
-
-    print "Adding 2, 30.0"
-    value.add(2, 30.0)
-    print value.get()
-
-    print "Adding 2, 55.0"
-    value.add(2, 55.0)
-    print value.get()
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/airports.py
===================================================================
--- src/mlx/airports.py	(revision 325)
+++ src/mlx/airports.py	(revision 325)
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+import sys
+
+#-----------------------------------------------------------------------------
+
+## @package mlx.airports
+#
+# The mapping of ICAO codes to airport names.
+#
+# This module contains the mapping of ICAO codes to airport cities and names,
+# as well as a \ref getWelcomeMessage "function" to produce a welcome message
+# for a certain airport.
+
+#-----------------------------------------------------------------------------
+
+## The mapping of the ICAO codes
+airportNames = {
+    "VTBS" : ("Bangkok", "Suvarnabhumi"),
+    "KJFK" : ("New York", "J. F. Kennedy"),
+    "CYYZ" : ("Toronto", "Lester B. Pearson"),
+    "UUEE" : ("Moscow", "Sheremetevo"),
+    "UKBB" : ("Kiev", "Borispol"),
+    "UKOO" : ("Odessa", None),
+    "USSS" : ("Yekaterinburg", "Koltsovo"),
+    "LTBA" : ("Istanbul", "Atatürk"),
+    "LLBG" : ("Tel-Aviv", "Ben Gurion"),
+    "HECA" : ("Cairo", None),
+    "LCLK" : ("Larnaca", None),
+    "LGAV" : ("Athens", "Eleftherios Venizelos"),
+    "OLBA" : ("Beirut", "Rafic Hariri"),
+    "OSDI" : ("Damascus", None),
+    "LGTS" : ("Thessaloniki", "Makedonia"),
+    "LIRF" : ("Rome", "Fiumicino"),
+    "LIPZ" : ("Venezia", "Tessera"),
+    "LATI" : ("Tirana", "Rinas"),
+    "LWSK" : ("Skopje", None),
+    "LQSA" : ("Sarajevo", "Butmir"),
+    "BKPR" : ("Pristina", None),
+    "LDZA" : ("Zagreb", "Pleso"),
+    "LDSP" : ("Split", "Kastela"),
+    "LDDU" : ("Dubrovnik", "Cilipi"),
+    "LYPG" : ("Podgorica", "Titograd"),
+    "EDDS" : ("Stuttgart Echterdingen", None),
+    "EDDF" : ("Frankfurt", "Main"),
+    "EDDM" : ("Munich", "Franz-Josef Strauss"),
+    "EDDH" : ("Hamburg", None),
+    "LFPG" : ("Paris", "Charles de Gaulle"),
+    "LFLL" : ("Lyon", "Satolas"),
+    "LSGG" : ("Geneva", "Cointrin"),
+    "LEMD" : ("Madrid", "Barajas"),
+    "EBBR" : ("Brussels", "Zaventem"),
+    "EGKK" : ("London", "Gatwick"),
+    "EIDW" : ("Dublin", "Collinstown"),
+    "EICK" : ("Cork", None),
+    "EHAM" : ("Amsterdam", "Schiphol"),
+    "EDDT" : ("Berlin", "Tegel"),
+    "EFHK" : ("Helsinki", "Vantaa"),
+    "EKCH" : ("Copenhagen", "Kastrup"),
+    "ESSA" : ("Stockholm", "Arlanda"),
+    "ESGG" : ("Gothenburg", None),
+    "LKPR" : ("Prague", "Ruznye"),
+    "LBSF" : ("Sofia", "Vrhazdebna"),
+    "EPWA" : ("Warsaw", "Okecie"),
+    "LROP" : ("Bucharest", "Otopeni"),
+    "LRTR" : ("Timisoara", None),
+    "LRCK" : ("Constanta", None),
+    "LRTM" : ("Tirgu Mures", "Vidrasau"),
+    "LHBP" : ("Budapest", "Ferihegy"),
+    "LHDC" : ("Debrecen", None)
+}
+
+#-----------------------------------------------------------------------------
+
+def getWelcomeMessage(icao):
+    """Get the welcome message for the airport with the given ICAO code."""
+    message = "Welcome to "
+    if icao in airportNames:
+        (town, airportName) = airportNames[icao]
+        message += town if airportName is None \
+                   else (town + " " + airportName + " Airport")
+    else:
+        message += icao
+    message += "."
+    return message
+
+#-----------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    for (town, airport) in airportNames.itervalues():
+        print town, airport
+    print getWelcomeMessage("LIRF")
+    print getWelcomeMessage("LHDC")
+    print getWelcomeMessage("LIRN")
+    print getWelcomeMessage("LHBP")
+    print getWelcomeMessage("EDDF")
+
+#-----------------------------------------------------------------------------
Index: src/mlx/airports.py
===================================================================
--- src/mlx/airports.py	(revision 325)
+++ 	(revision )
@@ -1,98 +1,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-
-#-----------------------------------------------------------------------------
-
-## @package mlx.airports
-#
-# The mapping of ICAO codes to airport names.
-#
-# This module contains the mapping of ICAO codes to airport cities and names,
-# as well as a \ref getWelcomeMessage "function" to produce a welcome message
-# for a certain airport.
-
-#-----------------------------------------------------------------------------
-
-## The mapping of the ICAO codes
-airportNames = {
-    "VTBS" : ("Bangkok", "Suvarnabhumi"),
-    "KJFK" : ("New York", "J. F. Kennedy"),
-    "CYYZ" : ("Toronto", "Lester B. Pearson"),
-    "UUEE" : ("Moscow", "Sheremetevo"),
-    "UKBB" : ("Kiev", "Borispol"),
-    "UKOO" : ("Odessa", None),
-    "USSS" : ("Yekaterinburg", "Koltsovo"),
-    "LTBA" : ("Istanbul", "Atatürk"),
-    "LLBG" : ("Tel-Aviv", "Ben Gurion"),
-    "HECA" : ("Cairo", None),
-    "LCLK" : ("Larnaca", None),
-    "LGAV" : ("Athens", "Eleftherios Venizelos"),
-    "OLBA" : ("Beirut", "Rafic Hariri"),
-    "OSDI" : ("Damascus", None),
-    "LGTS" : ("Thessaloniki", "Makedonia"),
-    "LIRF" : ("Rome", "Fiumicino"),
-    "LIPZ" : ("Venezia", "Tessera"),
-    "LATI" : ("Tirana", "Rinas"),
-    "LWSK" : ("Skopje", None),
-    "LQSA" : ("Sarajevo", "Butmir"),
-    "BKPR" : ("Pristina", None),
-    "LDZA" : ("Zagreb", "Pleso"),
-    "LDSP" : ("Split", "Kastela"),
-    "LDDU" : ("Dubrovnik", "Cilipi"),
-    "LYPG" : ("Podgorica", "Titograd"),
-    "EDDS" : ("Stuttgart Echterdingen", None),
-    "EDDF" : ("Frankfurt", "Main"),
-    "EDDM" : ("Munich", "Franz-Josef Strauss"),
-    "EDDH" : ("Hamburg", None),
-    "LFPG" : ("Paris", "Charles de Gaulle"),
-    "LFLL" : ("Lyon", "Satolas"),
-    "LSGG" : ("Geneva", "Cointrin"),
-    "LEMD" : ("Madrid", "Barajas"),
-    "EBBR" : ("Brussels", "Zaventem"),
-    "EGKK" : ("London", "Gatwick"),
-    "EIDW" : ("Dublin", "Collinstown"),
-    "EICK" : ("Cork", None),
-    "EHAM" : ("Amsterdam", "Schiphol"),
-    "EDDT" : ("Berlin", "Tegel"),
-    "EFHK" : ("Helsinki", "Vantaa"),
-    "EKCH" : ("Copenhagen", "Kastrup"),
-    "ESSA" : ("Stockholm", "Arlanda"),
-    "ESGG" : ("Gothenburg", None),
-    "LKPR" : ("Prague", "Ruznye"),
-    "LBSF" : ("Sofia", "Vrhazdebna"),
-    "EPWA" : ("Warsaw", "Okecie"),
-    "LROP" : ("Bucharest", "Otopeni"),
-    "LRTR" : ("Timisoara", None),
-    "LRCK" : ("Constanta", None),
-    "LRTM" : ("Tirgu Mures", "Vidrasau"),
-    "LHBP" : ("Budapest", "Ferihegy"),
-    "LHDC" : ("Debrecen", None)
-}
-
-#-----------------------------------------------------------------------------
-
-def getWelcomeMessage(icao):
-    """Get the welcome message for the airport with the given ICAO code."""
-    message = "Welcome to "
-    if icao in airportNames:
-        (town, airportName) = airportNames[icao]
-        message += town if airportName is None \
-                   else (town + " " + airportName + " Airport")
-    else:
-        message += icao
-    message += "."
-    return message
-
-#-----------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    for (town, airport) in airportNames.itervalues():
-        print town, airport
-    print getWelcomeMessage("LIRF")
-    print getWelcomeMessage("LHDC")
-    print getWelcomeMessage("LIRN")
-    print getWelcomeMessage("LHBP")
-    print getWelcomeMessage("EDDF")
-
-#-----------------------------------------------------------------------------
Index: src/mlx/checks.py
===================================================================
--- src/mlx/checks.py	(revision 666)
+++ src/mlx/checks.py	(revision 666)
@@ -0,0 +1,1640 @@
+
+import fs
+import const
+import util
+from acars import ACARS
+from sound import startSound
+
+import time
+
+#---------------------------------------------------------------------------------------
+
+## @package mlx.checks
+#
+# The classes that check the state of the aircraft.
+#
+# During the flight the program periodically queries various data from the
+# simulator. This data is returned in instances of the \ref
+# mlx.fs.AircraftState class and passed to the various "checkers",
+# i.e. instances of subclasses of the \ref StateChecker class. These checkers
+# perform various checks to see if the aircraft's parameters are within the
+# expected limits, or some of them just logs something.
+#
+# There are a few special ones, such as \ref StageChecker which computes the
+# transitions from one stage of the flight to the next one. Or \ref ACARSSender
+# which sends the ACARS periodically
+
+#---------------------------------------------------------------------------------------
+
+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_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 \
+                 (state.strobeLightsOn is False or
+                  (state.strobeLightsOn is None and state.xpdrC is False)) 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.cruiseAltitudeForDescent:
+                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<25.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 and 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 the ACARS are sent
+    INTERVAL = 3*60.0
+
+    def __init__(self, gui):
+        """Construct the ACARS sender."""
+        self._gui = gui
+        self._lastSent = None
+        self._sending = False
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """If the time has come to send the ACARS, send it."""
+        if self._sending:
+            return
+
+        now = time.time()
+
+        if self._lastSent is not None and \
+           (self._lastSent + ACARSSender.INTERVAL)>now:
+            return
+
+        self._sending = True
+        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"
+        self._sending = False
+
+#---------------------------------------------------------------------------------------
+
+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 %s" % \
+                           (flight.speedFromKnots(state.ias),
+                            flight.getEnglishSpeedUnit()))
+            logger.message(state.timestamp,
+                           "Takeoff heading: %03.0f degrees" % (state.heading,))
+            logger.message(state.timestamp,
+                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
+            logger.message(state.timestamp,
+                           "Takeoff flaps: %.0f" % (state.flapsSet))
+            logger.message(state.timestamp,
+                           "CG/Trim: %.1f%%/%.2f" % \
+                           (state.cog*100.0, state.elevatorTrim))
+
+
+            if abs(state.pitch)>aircraft.maxTakeOffPitch:
+                flight.handleNoGo("TOPitch", state.timestamp,
+                                  "Takeoff pitch higher than aircraft maximum (%.2f)" % \
+                                  (aircraft.maxTakeOffPitch,),
+                                  "TO TAILSTRIKE NO GO")
+
+            self._onTheGround = False
+
+#---------------------------------------------------------------------------------------
+
+class InitialClimbSpeedLogger(StateChecker):
+    """Logger for the initial climb speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._logged = False
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the initial climb speed if the altitude is reached."""
+        if not self._logged and \
+           state.radioAltitude>=aircraft.initialClimbSpeedAltitude and \
+           flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB]:
+            logger.message(state.timestamp,
+                           "Initial climb speed: %.0f %s - %.0f %s AGL" % \
+                           (flight.speedFromKnots(state.ias),
+                            flight.getEnglishSpeedUnit(),
+                            flight.aglFromFeet(state.radioAltitude),
+                            flight.getEnglishAGLUnit()))
+            self._logged = True
+
+#---------------------------------------------------------------------------------------
+
+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 %s" %
+                                   (flight.speedFromKnots(state.ias),
+                                    flight.getEnglishSpeedUnit()))
+                self._lastTime = state.timestamp
+
+#---------------------------------------------------------------------------------------
+
+class SpoilerLogger(StateChecker):
+    """Logger for the spoiler."""
+    def __init__(self):
+        """Construct the logger."""
+        self._spoilersDown = True
+        self._logged = False
+        self._spoilersExtension = None
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        spoilersDown = state.spoilersExtension==0
+
+        if flight.stage==const.STAGE_LANDING and state.onTheGround:
+            if not self._logged:
+                if not spoilersDown and self._spoilersDown:
+                    logger.message(state.timestamp, "Speedbrake deployed")
+                    self._logged = True
+                    config = flight.config
+                    if config.enableSounds and config.speedbrakeAtTD:
+                        startSound(const.SOUND_SPEEDBRAKE)
+        elif spoilersDown!=self._spoilersDown:
+            logger.message(state.timestamp,
+                           "Speedbrake " + ("down" if spoilersDown else "up"))
+
+        self._spoilersDown = spoilersDown
+
+#---------------------------------------------------------------------------------------
+
+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 ApproachCalloutsPlayer(StateChecker):
+    """A state checker that plays a sequence of approach callouts.
+
+    It tracks the altitude during the descent and landing phases and
+    if the altitude crosses one that has a callout associated with and
+    the vertical speed is negative, that callout will be played."""
+    def __init__(self, approachCallouts):
+        """Construct the approach callouts player."""
+        self._approachCallouts = approachCallouts
+        self._altitudes = approachCallouts.getAltitudes(descending = False)
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check if we need to play a callout."""
+        if (flight.stage==const.STAGE_DESCENT or \
+            flight.stage==const.STAGE_LANDING) and state.vs<0:
+            oldRadioAltitude = oldState.radioAltitude
+            radioAltitude = state.radioAltitude
+            for altitude in self._altitudes:
+                if radioAltitude<=altitude and \
+                   oldRadioAltitude>altitude:
+                    startSound(self._approachCallouts[altitude])
+                    break
+
+#---------------------------------------------------------------------------------------
+
+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, excludedStages = None):
+        """Construct the logger.
+
+        If logInitial is True, the initial value will be logged, not just the
+        changes later.
+
+        If excludedStages is given, it should be a list containing those stages
+        during which the changes should not be logged.
+
+        Child classes should define the following functions:
+        - _changed(self, oldState, state): returns a boolean indicating if the
+        value has changed or not
+        - _getMessage(self, flight, state, forced): return a strings containing
+        the message to log with the new value
+        """
+        self._logInitial = logInitial
+        self._excludedStages = [] if excludedStages is None else excludedStages
+
+    def _getLogTimestamp(self, state, forced):
+        """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."""
+        if flight.stage in self._excludedStages:
+            return
+
+        shouldLog = False
+        if oldState is None:
+            shouldLog = self._logInitial
+        else:
+            shouldLog = self._changed(oldState, state)
+
+        if shouldLog:
+            self.logState(flight, logger, state)
+
+    def logState(self, flight, logger, state, forced = False):
+        """Log the state."""
+        message = self._getMessage(flight, state, forced)
+        if message is not None:
+            logger.message(self._getLogTimestamp(state, forced), message)
+
+#-------------------------------------------------------------------------------
+
+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."""
+        currentValue = self._getValue(state)
+        return currentValue is not None and self._getValue(oldState)!=currentValue
+
+#---------------------------------------------------------------------------------------
+
+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
+        self._lastLoggedValue = None
+
+        self._logState = lambda flight, logger, state, forced = False: \
+            StateChangeLogger.logState(self, flight, logger, state,
+                                       forced = forced)
+        self.logState = lambda flight, logger, state, forced = False: \
+            DelayedChangeMixin.logState(self, flight, logger, state,
+                                        forced = forced)
+
+    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._isDifferent(self._oldValue, newValue):
+            if self._lastLoggedValue is not None and \
+               not self._isDifferent(self._lastLoggedValue, newValue):
+                self._firstChange = None
+            else:
+                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, forced):
+        """Get the log timestamp."""
+        return self._lastChangeState.timestamp \
+               if not forced and self._lastChangeState is not None \
+               else state.timestamp
+
+    def _isDifferent(self, oldValue, newValue):
+        """Determine if the given values are different.
+
+        This default implementation checks for simple equality."""
+        return oldValue!=newValue
+
+    def logState(self, flight, logger, state, forced):
+        """Log the given state.
+
+        The value belonging to that state is recorded in _lastLoggedValue.
+
+        It calls _logState to perform the real logging."""
+        self._lastLoggedValue = self._getValue(state)
+        self._logState(flight, logger, state, forced = forced)
+
+#---------------------------------------------------------------------------------------
+
+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, flight, state, forced):
+        """Get the message."""
+        value = self._getValue(state)
+        return None if value is None else self._template % (value,)
+
+#---------------------------------------------------------------------------------------
+
+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,
+                 excludedStages = None, minDelay = 0.0, maxDelay = 0.0):
+        """Construct the object."""
+        StateChangeLogger.__init__(self, logInitial = logInitial,
+                                   excludedStages = excludedStages)
+        SingleValueMixin.__init__(self, attrName)
+        DelayedChangeMixin.__init__(self, minDelay = minDelay,
+                                    maxDelay = maxDelay)
+        TemplateMessageMixin.__init__(self, template)
+        self._getLogTimestamp = \
+           lambda state, forced: \
+           DelayedChangeMixin._getLogTimestamp(self, state, forced)
+
+#---------------------------------------------------------------------------------------
+
+class ForceableLoggerMixin(object):
+    """A mixin for loggers that can be forced to log a certain state."""
+    def forceLog(self, flight, logger, state):
+        """Force logging the given state."""
+        self.logState(flight, logger, state, forced = True)
+
+#---------------------------------------------------------------------------------------
+
+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, forced: \
+            DelayedChangeMixin._getLogTimestamp(self, state, forced)
+
+    def _getMessage(self, flight, state, forced):
+        """Get the message to log on a change."""
+        logState = self._lastChangeState if \
+                   self._lastChangeState is not None else state
+        message = "Altimeter: %.2f hPa at %.0f feet" % \
+                  (logState.altimeter, logState.altitude)
+        if not logState.altimeterReliable:
+            message += " (u/r)"
+        return message
+
+#---------------------------------------------------------------------------------------
+
+class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
+    """Logger for NAV radios.
+
+    It also logs the OBS radial set."""
+    excludedStages = [const.STAGE_BOARDING, const.STAGE_PUSHANDTAXI,
+                      const.STAGE_RTO, const.STAGE_TAXIAFTERLAND,
+                      const.STAGE_PARKING]
+
+    @staticmethod
+    def getMessage(logName, frequency, obs):
+        """Get the message for the given NAV radio setting."""
+        message = u"%-5s %s" % (logName + ":", frequency)
+        if obs is not None: message += u" (%03d\u00b0)" % (obs,)
+        return message
+
+    def __init__(self, attrName, logName):
+        """Construct the NAV logger."""
+        StateChangeLogger.__init__(self, logInitial = False,
+                                   excludedStages = self.excludedStages)
+        DelayedChangeMixin.__init__(self)
+
+        self._getLogTimestamp = \
+            lambda state, forced: \
+            DelayedChangeMixin._getLogTimestamp(self, state, forced)
+
+        self._attrName = attrName
+        self._logName = logName
+
+    def _getValue(self, state):
+        """Get the value.
+
+        If both the frequency and the obs settings are available, a tuple
+        containing them is returned, otherwise None."""
+        frequency = getattr(state, self._attrName)
+        obs = getattr(state, self._attrName + "_obs")
+        manual = getattr(state, self._attrName + "_manual")
+        return (frequency, obs, manual)
+
+    def _getMessage(self, flight, state, forced):
+        """Get the message."""
+        (frequency, obs, manual) = self._getValue(state)
+        return None if frequency is None or obs is None or \
+               (not manual and not forced) else \
+               self.getMessage(self._logName, frequency, obs)
+
+    def _isDifferent(self, oldValue, newValue):
+        """Determine if the valie has changed between the given states."""
+        (oldFrequency, oldOBS, _oldManual) = oldValue
+        (newFrequency, newOBS, _newManual) = newValue
+        return oldFrequency!=newFrequency or oldOBS!=newOBS
+
+#---------------------------------------------------------------------------------------
+
+class ILSLogger(NAVLogger):
+    """Logger for the ILS radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(ILSLogger, self).__init__("ils", "ILS")
+
+#---------------------------------------------------------------------------------------
+
+class NAV1Logger(NAVLogger):
+    """Logger for the NAV1 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV1Logger, self).__init__("nav1", "NAV1")
+
+#---------------------------------------------------------------------------------------
+
+class NAV2Logger(NAVLogger):
+    """Logger for the NAV2 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV2Logger, self).__init__("nav2", "NAV2")
+
+#---------------------------------------------------------------------------------------
+
+class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
+    """Base class for the ADF loggers."""
+    def __init__(self, attr, logName):
+        """Construct the ADF logger."""
+        GenericStateChangeLogger.__init__(self, attr,
+                                          "%s: %%s" % (logName,),
+                                          logInitial = False,
+                                          excludedStages =
+                                          NAVLogger.excludedStages,
+                                          minDelay = 3.0, maxDelay = 10.0)
+
+#---------------------------------------------------------------------------------------
+
+class ADF1Logger(ADFLogger):
+    """Logger for the ADF1 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(ADF1Logger, self).__init__("adf1", "ADF1")
+
+#---------------------------------------------------------------------------------------
+
+class ADF2Logger(ADFLogger):
+    """Logger for the ADF2 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(ADF2Logger, self).__init__("adf2", "ADF2")
+
+#---------------------------------------------------------------------------------------
+
+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, DelayedChangeMixin):
+    """Base class for the loggers of the various lights."""
+    def __init__(self, attrName, template):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self)
+        SingleValueMixin.__init__(self, attrName)
+        DelayedChangeMixin.__init__(self)
+        self._getLogTimestamp = \
+            lambda state, forced: \
+            DelayedChangeMixin._getLogTimestamp(self, state, forced)
+
+        self._template = template
+
+    def _getMessage(self, flight, state, forced):
+        """Get the message from the given state."""
+        value = self._getValue(state)
+        if value is None:
+            return None
+        else:
+            return self._template % ("ON" if value 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, DelayedChangeMixin):
+    """Logger for the flaps setting."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True,
+                                   excludedStages =
+                                   [const.STAGE_BOARDING,
+                                    const.STAGE_PUSHANDTAXI,
+                                    const.STAGE_RTO,
+                                    const.STAGE_TAKEOFF])
+        SingleValueMixin.__init__(self, "flapsSet")
+        DelayedChangeMixin.__init__(self)
+        self._getLogTimestamp = \
+            lambda state, forced: \
+            DelayedChangeMixin._getLogTimestamp(self, state, forced)
+
+    def _getMessage(self, flight, state, forced):
+        """Get the message to log on a change."""
+        logState = self._lastChangeState if \
+                   self._lastChangeState is not None else state
+        speed = logState.groundSpeed if logState.groundSpeed<80.0 \
+                else logState.ias
+        message = "Flaps %.0f - %.0f %s, %.0f %s AGL, %.0f ft AMSL" % \
+               (logState.flapsSet, flight.speedFromKnots(speed),
+                flight.getEnglishSpeedUnit(),
+                flight.aglFromFeet(logState.radioAltitude),
+                flight.getEnglishAGLUnit(),
+                logState.altitude)
+
+        return message
+
+#---------------------------------------------------------------------------------------
+
+class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Logger for the gears state."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "gearControlDown")
+
+    def _getMessage(self, flight, state, forced):
+        """Get the message to log on a change."""
+        return "Gears SET to %s at %.0f %s, %.0f ft, %.0f %s AGL" % \
+            ("DOWN" if state.gearControlDown else "UP",
+             flight.speedFromKnots(state.ias),
+             flight.getEnglishSpeedUnit(),
+             state.altitude,
+             flight.aglFromFeet(state.radioAltitude),
+             flight.getEnglishAGLUnit())
+
+#---------------------------------------------------------------------------------------
+
+class APLogger(StateChangeLogger, DelayedChangeMixin):
+    """Log the state of the autopilot."""
+    @staticmethod
+    def _logBoolean(logger, timestamp, what, value):
+        """Log a boolean value.
+
+        what is the name of the value that is being logged."""
+        message = what + " "
+        if value is None:
+            message += "cannot be detected, will not log"
+        else:
+            message += "is " + ("ON" if value else "OFF")
+        logger.message(timestamp, message)
+
+    @staticmethod
+    def _logNumeric(logger, timestamp, what, format, value):
+        """Log a numerical value."""
+        message = what
+        if value is None:
+            message += u" cannot be detected, will not log"
+        else:
+            message += u": " + format % (value,)
+        logger.message(timestamp, message)
+
+    @staticmethod
+    def _logAPMaster(logger, timestamp, state):
+        """Log the AP master state."""
+        APLogger._logBoolean(logger, timestamp, "AP master",
+                             state.apMaster)
+
+    @staticmethod
+    def _logAPHeadingHold(logger, timestamp, state):
+        """Log the AP heading hold state."""
+        APLogger._logBoolean(logger, timestamp, "AP heading hold",
+                             state.apHeadingHold)
+
+    @staticmethod
+    def _logAPHeading(logger, timestamp, state):
+        """Log the AP heading."""
+        APLogger._logNumeric(logger, timestamp, u"AP heading",
+                             u"%03.0f\u00b0", state.apHeading)
+
+    @staticmethod
+    def _logAPAltitudeHold(logger, timestamp, state):
+        """Log the AP altitude hold state."""
+        APLogger._logBoolean(logger, timestamp, "AP altitude hold",
+                             state.apAltitudeHold)
+
+    @staticmethod
+    def _logAPAltitude(logger, timestamp, state):
+        """Log the AP heading."""
+        APLogger._logNumeric(logger, timestamp, u"AP altitude",
+                             u"%.0f ft", state.apAltitude)
+
+    def __init__(self):
+        """Construct the state logger."""
+        StateChangeLogger.__init__(self)
+        DelayedChangeMixin.__init__(self)
+        self._lastLoggedState = None
+        self.logState = lambda flight, logger, state:\
+            APLogger.logState(self, flight, logger, state)
+
+    def _getValue(self, state):
+        """Convert the relevant values from the given state into a tuple."""
+        return (state.apMaster,
+                state.apHeadingHold, state.apHeading,
+                state.apAltitudeHold, state.apAltitude)
+
+    def _isDifferent(self, oldValue, newValue):
+        """Determine if the given old and new values are different (enough) to
+        be logged."""
+        (oldAPMaster, oldAPHeadingHold, oldAPHeading,
+         oldAPAltitudeHold, oldAPAltitude) = oldValue
+        (apMaster, apHeadingHold, apHeading,
+         apAltitudeHold, apAltitude) = newValue
+
+        if apMaster is not None and apMaster!=oldAPMaster:
+            return True
+        if apMaster is False:
+            return False
+
+        if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
+            return True
+        if apHeadingHold is not False and apHeading is not None and \
+            apHeading!=oldAPHeading:
+            return True
+
+        if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
+            return True
+        if apAltitudeHold is not False and apAltitude is not None and \
+            apAltitude!=oldAPAltitude:
+            return True
+
+        return False
+
+    def logState(self, flight, logger, state):
+        """Log the autopilot state."""
+        timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
+        if self._lastLoggedState is None:
+            self._logAPMaster(logger, timestamp, state)
+            if state.apMaster is not False or state.apHeadingHold is None:
+                self._logAPHeadingHold(logger, timestamp, state)
+            if state.apMaster is not False and \
+               (state.apHeadingHold is not False or state.apHeading is None):
+                self._logAPHeading(logger, timestamp, state)
+            if state.apMaster is not False or state.apAltitudeHold is None:
+                self._logAPAltitudeHold(logger, timestamp, state)
+            if state.apMaster is not False and \
+               (state.apAltitudeHold is not False or state.apAltitude is None):
+                self._logAPAltitude(logger, timestamp, state)
+            self._firstCall = False
+        else:
+            oldState = self._lastLoggedState
+
+            apMasterTurnedOn = False
+            if state.apMaster is not None and state.apMaster!=oldState.apMaster:
+                apMasterTurnedOn = state.apMaster
+                self._logAPMaster(logger, timestamp, state)
+
+            if state.apMaster is not False:
+                apHeadingHoldTurnedOn = False
+                if state.apHeadingHold is not None and \
+                   (state.apHeadingHold!=oldState.apHeadingHold or
+                    apMasterTurnedOn):
+                    apHeadingHoldTurnedOn = state.apHeadingHold
+                    self._logAPHeadingHold(logger, timestamp, state)
+
+                if state.apHeadingHold is not False and \
+                   state.apHeading is not None and \
+                   (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
+                    apHeadingHoldTurnedOn):
+                    self._logAPHeading(logger, timestamp, state)
+
+                apAltitudeHoldTurnedOn = False
+                if state.apAltitudeHold is not None and \
+                   (state.apAltitudeHold!=oldState.apAltitudeHold or
+                    apMasterTurnedOn):
+                    apAltitudeHoldTurnedOn = state.apAltitudeHold
+                    self._logAPAltitudeHold(logger, timestamp, state)
+
+                if state.apAltitudeHold is not False and \
+                   state.apAltitude is not None and \
+                   (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
+                    apAltitudeHoldTurnedOn):
+                    self._logAPAltitude(logger, timestamp, state)
+
+        self._lastLoggedState = state
+
+#---------------------------------------------------------------------------------------
+
+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."""
+        return (not flight.config.usingFS2Crew or not state.parking or
+                flight.stage!=const.STAGE_TAXIAFTERLAND) and \
+                state.antiCollisionLightsOn is False and \
+                self.isEngineCondition(state)
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Anti-collision lights were %s" %
+                                                      ("on" if state.antiCollisionLightsOn else "off",)),
+                           1)
+
+    def isEngineCondition(self, state):
+        """Determine if the engines are in such a state that the lights should
+        be on."""
+        if state.n1 is not None:
+            return max(state.n1)>5
+        elif state.rpm is not None:
+            return max(state.rpm)>0
+        else:
+            return False
+
+#---------------------------------------------------------------------------------------
+
+class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
+    """Check for the anti-collision light for Tuplev planes."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        numEnginesRunning = 0
+        for n1 in state.n1:
+            if n1>5: numEnginesRunning += 1
+
+        if flight.stage==const.STAGE_PARKING or \
+           flight.stage==const.STAGE_TAXIAFTERLAND:
+            return numEnginesRunning<len(state.n1) \
+                   and state.antiCollisionLightsOn
+        else:
+            return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
+                   numEnginesRunning<1 and state.antiCollisionLightsOn
+
+#---------------------------------------------------------------------------------------
+
+class TupolevLandingLightsChecker(PatientFaultChecker):
+    """Check if the landing light is not switched on above an IAS of 340 km/h."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.landingLightsOn and state.ias>(340.0*const.KMPHTOKNOTS)
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(TupolevLandingLightsChecker, state.timestamp,
+                           "The landing lights were on above an IAS of 340 km/h",
+                           1)
+
+#---------------------------------------------------------------------------------------
+
+class BankChecker(SimpleFaultChecker):
+    """Check for the bank is within limits."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if flight.stage==const.STAGE_CRUISE:
+            isDH8DXplane = flight.aircraftType==const.AIRCRAFT_DH8D and \
+                           (flight.fsType==const.SIM_XPLANE10 or
+                            flight.fsType==const.SIM_XPLANE9)
+            bankLimit = 35 if isDH8DXplane else 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."""
+        message = "Bank too steep (%.1f)" % (state.bank,)
+        flight.handleFault(BankChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, message),
+                           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 and
+            aircraft.type!=const.AIRCRAFT_F70) 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.smoothedIAS>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.smoothedIAS>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 not state.onTheGround 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 state.landingLightsOn is not None and \
+               ((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 and
+                 state.ias>50.0) 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 in [const.STAGE_TAKEOFF,
+                                      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 TransponderChecker(PatientFaultChecker):
+    """Check if the transponder is used properly."""
+    def __init__(self):
+        """Construct the transponder checker."""
+        super(TransponderChecker, self).__init__()
+        self._liftOffTime = None
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if state.onTheGround:
+            self._liftOffTime = None
+        elif self._liftOffTime is None:
+            self._liftOffTime = state.timestamp
+
+        return state.xpdrC is not None and \
+               ((state.xpdrC and flight.stage in
+                 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
+                (not state.xpdrC and
+                 (flight.stage in
+                  [const.STAGE_CRUISE, const.STAGE_DESCENT,
+                   const.STAGE_GOAROUND] or \
+                  (flight.stage==const.STAGE_LANDING and
+                   state.groundSpeed>50.0) or \
+                  ((not state.autoXPDR or \
+                    (self._liftOffTime is not None and
+                     state.timestamp > (self._liftOffTime+8))) and \
+                   ((flight.stage==const.STAGE_TAKEOFF and
+                     not state.onTheGround) or flight.stage==const.STAGE_CLIMB))
+                  )
+                 )
+                )
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 0
+        message = "Transponder was %s" % \
+                  (("mode C" if state.xpdrC else "standby"),)
+        flight.handleFault(TransponderChecker, 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 and \
+                               not flight.entranceExam 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 and \
+                             not flight.entranceExam 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 if not flight.entranceExam else None
+
+    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 \
+               state.navLightsOn is False
+
+    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 WindSensitiveFaultChecker(FaultChecker):
+    """A fault checker which checks for a fault condition that might arise due
+    to suddenly changing winds.
+
+    If the condition is detected, its fact is logged, and the winds are also
+    logged repeatedly until and for a while after the condition has ceased. A
+    fault is logged only if the condition holds for a certain period of
+    time."""
+    # The time the winds were logged last. This is global to avoid duplicate
+    # logging if several wind-sensitive fault conditions gold
+    _windsLastLogged = None
+
+    # The wind logging interval
+    _windsLogInterval = 5.0
+
+    def __init__(self, faultTimeout = 30.0, afterLoggingTime = 20.0):
+        """Construct the fault checker."""
+        self._faultTimeout = faultTimeout
+        self._afterLoggingTime = afterLoggingTime
+
+        self._faultStarted = None
+        self._faultCeased = None
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check for the condition and do whatever is needed."""
+        timestamp = state.timestamp
+        logWinds = False
+
+        if self.isCondition(flight, aircraft, oldState, state):
+            logWinds = True
+            if self._faultStarted is None:
+                self._faultStarted = timestamp
+                self._faultCeased = None
+                self.logCondition(flight, aircraft, logger, oldState, state,
+                                  False)
+
+                WindSensitiveFaultChecker._windsLastLogged = None
+
+            elif timestamp >= (self._faultStarted + self._faultTimeout):
+                self.logCondition(flight, aircraft, logger, oldState, state,
+                                  True)
+                self._faultStarted = timestamp
+
+        else:
+            if self._faultStarted is not None and self._faultCeased is None:
+                self._faultCeased = timestamp
+                self._faultStarted = None
+
+            if self._faultCeased is not None:
+                if timestamp < (self._faultCeased + self._afterLoggingTime):
+                    logWinds = True
+                else:
+                    self._faultCeased = None
+
+        if logWinds and \
+           (WindSensitiveFaultChecker._windsLastLogged is None or
+            timestamp >= (WindSensitiveFaultChecker._windsLastLogged +
+                          self._windsLogInterval)):
+            logger.message(timestamp, "Winds: %.f knots from %.f" %
+                           (state.windSpeed, state.windDirection))
+            WindSensitiveFaultChecker._windsLastLogged = timestamp
+
+#---------------------------------------------------------------------------------------
+
+class OverspeedChecker(WindSensitiveFaultChecker):
+    """Check if Vne has been exceeded."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.overspeed
+
+    def logCondition(self, flight, aircraft, logger, oldState, state, isFault):
+        """Log the condition fault."""
+        if isFault:
+            flight.handleFault(OverspeedChecker, state.timestamp,
+                               FaultChecker._appendDuring(flight, "Overspeed"),
+                               20)
+        else:
+            logger.message(state.timestamp,
+                           FaultChecker._appendDuring(flight, "Overspeed"))
+
+#---------------------------------------------------------------------------------------
+
+class StallChecker(WindSensitiveFaultChecker):
+    """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 logCondition(self, flight, aircraft, logger, oldState, state, isFault):
+        """Log the condition."""
+        if isFault:
+            score = 40 if flight.stage in [const.STAGE_TAKEOFF,
+                                           const.STAGE_LANDING] else 30
+            flight.handleFault(StallChecker, state.timestamp,
+                               FaultChecker._appendDuring(flight, "Stalled"),
+                               score)
+        else:
+            logger.message(state.timestamp,
+                            FaultChecker._appendDuring(flight, "Stalled"))
+
+#---------------------------------------------------------------------------------------
+
+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 not flight.entranceExam and \
+               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 state.pitotHeatOn is False
+
+    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 ReverserLogger(StateChecker):
+    """Logger for the reverser."""
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if oldState is not None and state.reverser != oldState.reverser:
+            reverser = max(state.reverser)
+            logger.message(state.timestamp,
+                           "Reverser " + ("unlocked" if reverser else "closed") +
+                           (" - %.0f %s" % (flight.speedFromKnots(state.ias),
+                                            flight.getEnglishSpeedUnit())))
+
+#---------------------------------------------------------------------------------------
+
+class ReverserChecker(SimpleFaultChecker):
+    """Check if the reverser is not used below the speed prescribed for the
+    aircraft."""
+    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.reverser and \
+            state.ias<aircraft.reverseMinSpeed and max(state.reverser)
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        message = "Reverser used below %.0f %s" % \
+                  (flight.speedFromKnots(aircraft.reverseMinSpeed), flight.getEnglishSpeedUnit())
+        flight.handleFault(ReverserChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight, message),
+                           15)
+
+#---------------------------------------------------------------------------------------
+
+class SpeedChecker(StateChecker):
+    """Checker for the ground speed of aircraft.
+
+    If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state is
+    saved as a provisional takeoff state. If the speed then decreases below 50
+    knots, or the plane remains on the ground for more than 40 seconds, a taxi
+    speed error is logged with the highest ground speed detected. This state is
+    also stored in the flight object as a possible
+
+    If the plane becomes airborne within 40 seconds, the stage becomes TAKEOFF,
+    and the previously saved takeoff state is logged.
+
+    During the TAXIAFTERLAND stage, if the speed goes above 50 knots, a fault
+    is logged based on the actual speed."""
+    @staticmethod
+    def logSpeedFault(flight, state, stage = None, updateID = None):
+        """Log the speed fault."""
+        message = "Taxi speed over %.0f %s" % \
+                  (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
+        if stage is None:
+            stage = flight.stage
+        score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
+        return flight.handleFault((SpeedChecker, stage), state.timestamp,
+                                  FaultChecker._appendDuring(flight, message),
+                                  score, updatePrevious = updateID is None,
+                                  updateID = updateID)
+
+    def __init__(self):
+        """Initialize the speed checker."""
+        self._takeoffState = None
+        self._highestSpeedState = None
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check the state as described above."""
+        if flight.stage==const.STAGE_PUSHANDTAXI or \
+           flight.stage==const.STAGE_RTO:
+            if state.groundSpeed>50 and aircraft.hasStrobeLight and \
+               state.strobeLightsOn is False:
+                self.logSpeedFault(flight, state)
+            else:
+                self._checkPushAndTaxi(flight, aircraft, state)
+        elif flight.stage==const.STAGE_TAXIAFTERLAND:
+            if state.groundSpeed>50:
+                self.logSpeedFault(flight, state)
+        else:
+            self._takeoffState = None
+
+    def _checkPushAndTaxi(self, flight, aircraft, state):
+        """Check the speed during the push and taxi stage."""
+        if state.groundSpeed>50:
+            if self._takeoffState is None:
+                self._takeoffState = state
+                self._highestSpeedState = state
+            else:
+                if state.groundSpeed>self._highestSpeedState.groundSpeed:
+                    self._highestSpeedState = state
+
+                if not state.onTheGround:
+                    aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
+                    self._takeoffState = None
+                elif state.timestamp > (self._takeoffState.timestamp + 60):
+                    flight.setRTOState(self._highestSpeedState)
+        elif self._takeoffState is not None:
+            flight.setRTOState(self._highestSpeedState)
+            self._takeoffState = None
+
+#---------------------------------------------------------------------------------------
+
+class StrobeLightsChecker(PatientFaultChecker):
+    """Check if the strobe lights are used properly."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.strobeLightsOn is not None and \
+              ((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 \
+               state.n1 is not None and max(state.n1)>97
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        flight.handleFault(ThrustChecker, state.timestamp,
+                           FaultChecker._appendDuring(flight,
+                                                      "Thrust setting was too high (>97%)"),
+                           FaultChecker._getLinearScore(97, 110, 0, 10,
+                                                        max(state.n1)),
+                           updatePrevious = True)
+
+#---------------------------------------------------------------------------------------
+
+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.smoothedVS
+        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.smoothedVS
+
+        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 666)
+++ 	(revision )
@@ -1,1640 +1,0 @@
-
-import fs
-import const
-import util
-from acars import ACARS
-from sound import startSound
-
-import time
-
-#---------------------------------------------------------------------------------------
-
-## @package mlx.checks
-#
-# The classes that check the state of the aircraft.
-#
-# During the flight the program periodically queries various data from the
-# simulator. This data is returned in instances of the \ref
-# mlx.fs.AircraftState class and passed to the various "checkers",
-# i.e. instances of subclasses of the \ref StateChecker class. These checkers
-# perform various checks to see if the aircraft's parameters are within the
-# expected limits, or some of them just logs something.
-#
-# There are a few special ones, such as \ref StageChecker which computes the
-# transitions from one stage of the flight to the next one. Or \ref ACARSSender
-# which sends the ACARS periodically
-
-#---------------------------------------------------------------------------------------
-
-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_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 \
-                 (state.strobeLightsOn is False or
-                  (state.strobeLightsOn is None and state.xpdrC is False)) 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.cruiseAltitudeForDescent:
-                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<25.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 and 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 the ACARS are sent
-    INTERVAL = 3*60.0
-
-    def __init__(self, gui):
-        """Construct the ACARS sender."""
-        self._gui = gui
-        self._lastSent = None
-        self._sending = False
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """If the time has come to send the ACARS, send it."""
-        if self._sending:
-            return
-
-        now = time.time()
-
-        if self._lastSent is not None and \
-           (self._lastSent + ACARSSender.INTERVAL)>now:
-            return
-
-        self._sending = True
-        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"
-        self._sending = False
-
-#---------------------------------------------------------------------------------------
-
-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 %s" % \
-                           (flight.speedFromKnots(state.ias),
-                            flight.getEnglishSpeedUnit()))
-            logger.message(state.timestamp,
-                           "Takeoff heading: %03.0f degrees" % (state.heading,))
-            logger.message(state.timestamp,
-                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
-            logger.message(state.timestamp,
-                           "Takeoff flaps: %.0f" % (state.flapsSet))
-            logger.message(state.timestamp,
-                           "CG/Trim: %.1f%%/%.2f" % \
-                           (state.cog*100.0, state.elevatorTrim))
-
-
-            if abs(state.pitch)>aircraft.maxTakeOffPitch:
-                flight.handleNoGo("TOPitch", state.timestamp,
-                                  "Takeoff pitch higher than aircraft maximum (%.2f)" % \
-                                  (aircraft.maxTakeOffPitch,),
-                                  "TO TAILSTRIKE NO GO")
-
-            self._onTheGround = False
-
-#---------------------------------------------------------------------------------------
-
-class InitialClimbSpeedLogger(StateChecker):
-    """Logger for the initial climb speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._logged = False
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the initial climb speed if the altitude is reached."""
-        if not self._logged and \
-           state.radioAltitude>=aircraft.initialClimbSpeedAltitude and \
-           flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB]:
-            logger.message(state.timestamp,
-                           "Initial climb speed: %.0f %s - %.0f %s AGL" % \
-                           (flight.speedFromKnots(state.ias),
-                            flight.getEnglishSpeedUnit(),
-                            flight.aglFromFeet(state.radioAltitude),
-                            flight.getEnglishAGLUnit()))
-            self._logged = True
-
-#---------------------------------------------------------------------------------------
-
-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 %s" %
-                                   (flight.speedFromKnots(state.ias),
-                                    flight.getEnglishSpeedUnit()))
-                self._lastTime = state.timestamp
-
-#---------------------------------------------------------------------------------------
-
-class SpoilerLogger(StateChecker):
-    """Logger for the spoiler."""
-    def __init__(self):
-        """Construct the logger."""
-        self._spoilersDown = True
-        self._logged = False
-        self._spoilersExtension = None
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        spoilersDown = state.spoilersExtension==0
-
-        if flight.stage==const.STAGE_LANDING and state.onTheGround:
-            if not self._logged:
-                if not spoilersDown and self._spoilersDown:
-                    logger.message(state.timestamp, "Speedbrake deployed")
-                    self._logged = True
-                    config = flight.config
-                    if config.enableSounds and config.speedbrakeAtTD:
-                        startSound(const.SOUND_SPEEDBRAKE)
-        elif spoilersDown!=self._spoilersDown:
-            logger.message(state.timestamp,
-                           "Speedbrake " + ("down" if spoilersDown else "up"))
-
-        self._spoilersDown = spoilersDown
-
-#---------------------------------------------------------------------------------------
-
-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 ApproachCalloutsPlayer(StateChecker):
-    """A state checker that plays a sequence of approach callouts.
-
-    It tracks the altitude during the descent and landing phases and
-    if the altitude crosses one that has a callout associated with and
-    the vertical speed is negative, that callout will be played."""
-    def __init__(self, approachCallouts):
-        """Construct the approach callouts player."""
-        self._approachCallouts = approachCallouts
-        self._altitudes = approachCallouts.getAltitudes(descending = False)
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check if we need to play a callout."""
-        if (flight.stage==const.STAGE_DESCENT or \
-            flight.stage==const.STAGE_LANDING) and state.vs<0:
-            oldRadioAltitude = oldState.radioAltitude
-            radioAltitude = state.radioAltitude
-            for altitude in self._altitudes:
-                if radioAltitude<=altitude and \
-                   oldRadioAltitude>altitude:
-                    startSound(self._approachCallouts[altitude])
-                    break
-
-#---------------------------------------------------------------------------------------
-
-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, excludedStages = None):
-        """Construct the logger.
-
-        If logInitial is True, the initial value will be logged, not just the
-        changes later.
-
-        If excludedStages is given, it should be a list containing those stages
-        during which the changes should not be logged.
-
-        Child classes should define the following functions:
-        - _changed(self, oldState, state): returns a boolean indicating if the
-        value has changed or not
-        - _getMessage(self, flight, state, forced): return a strings containing
-        the message to log with the new value
-        """
-        self._logInitial = logInitial
-        self._excludedStages = [] if excludedStages is None else excludedStages
-
-    def _getLogTimestamp(self, state, forced):
-        """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."""
-        if flight.stage in self._excludedStages:
-            return
-
-        shouldLog = False
-        if oldState is None:
-            shouldLog = self._logInitial
-        else:
-            shouldLog = self._changed(oldState, state)
-
-        if shouldLog:
-            self.logState(flight, logger, state)
-
-    def logState(self, flight, logger, state, forced = False):
-        """Log the state."""
-        message = self._getMessage(flight, state, forced)
-        if message is not None:
-            logger.message(self._getLogTimestamp(state, forced), message)
-
-#-------------------------------------------------------------------------------
-
-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."""
-        currentValue = self._getValue(state)
-        return currentValue is not None and self._getValue(oldState)!=currentValue
-
-#---------------------------------------------------------------------------------------
-
-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
-        self._lastLoggedValue = None
-
-        self._logState = lambda flight, logger, state, forced = False: \
-            StateChangeLogger.logState(self, flight, logger, state,
-                                       forced = forced)
-        self.logState = lambda flight, logger, state, forced = False: \
-            DelayedChangeMixin.logState(self, flight, logger, state,
-                                        forced = forced)
-
-    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._isDifferent(self._oldValue, newValue):
-            if self._lastLoggedValue is not None and \
-               not self._isDifferent(self._lastLoggedValue, newValue):
-                self._firstChange = None
-            else:
-                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, forced):
-        """Get the log timestamp."""
-        return self._lastChangeState.timestamp \
-               if not forced and self._lastChangeState is not None \
-               else state.timestamp
-
-    def _isDifferent(self, oldValue, newValue):
-        """Determine if the given values are different.
-
-        This default implementation checks for simple equality."""
-        return oldValue!=newValue
-
-    def logState(self, flight, logger, state, forced):
-        """Log the given state.
-
-        The value belonging to that state is recorded in _lastLoggedValue.
-
-        It calls _logState to perform the real logging."""
-        self._lastLoggedValue = self._getValue(state)
-        self._logState(flight, logger, state, forced = forced)
-
-#---------------------------------------------------------------------------------------
-
-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, flight, state, forced):
-        """Get the message."""
-        value = self._getValue(state)
-        return None if value is None else self._template % (value,)
-
-#---------------------------------------------------------------------------------------
-
-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,
-                 excludedStages = None, minDelay = 0.0, maxDelay = 0.0):
-        """Construct the object."""
-        StateChangeLogger.__init__(self, logInitial = logInitial,
-                                   excludedStages = excludedStages)
-        SingleValueMixin.__init__(self, attrName)
-        DelayedChangeMixin.__init__(self, minDelay = minDelay,
-                                    maxDelay = maxDelay)
-        TemplateMessageMixin.__init__(self, template)
-        self._getLogTimestamp = \
-           lambda state, forced: \
-           DelayedChangeMixin._getLogTimestamp(self, state, forced)
-
-#---------------------------------------------------------------------------------------
-
-class ForceableLoggerMixin(object):
-    """A mixin for loggers that can be forced to log a certain state."""
-    def forceLog(self, flight, logger, state):
-        """Force logging the given state."""
-        self.logState(flight, logger, state, forced = True)
-
-#---------------------------------------------------------------------------------------
-
-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, forced: \
-            DelayedChangeMixin._getLogTimestamp(self, state, forced)
-
-    def _getMessage(self, flight, state, forced):
-        """Get the message to log on a change."""
-        logState = self._lastChangeState if \
-                   self._lastChangeState is not None else state
-        message = "Altimeter: %.2f hPa at %.0f feet" % \
-                  (logState.altimeter, logState.altitude)
-        if not logState.altimeterReliable:
-            message += " (u/r)"
-        return message
-
-#---------------------------------------------------------------------------------------
-
-class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
-    """Logger for NAV radios.
-
-    It also logs the OBS radial set."""
-    excludedStages = [const.STAGE_BOARDING, const.STAGE_PUSHANDTAXI,
-                      const.STAGE_RTO, const.STAGE_TAXIAFTERLAND,
-                      const.STAGE_PARKING]
-
-    @staticmethod
-    def getMessage(logName, frequency, obs):
-        """Get the message for the given NAV radio setting."""
-        message = u"%-5s %s" % (logName + ":", frequency)
-        if obs is not None: message += u" (%03d\u00b0)" % (obs,)
-        return message
-
-    def __init__(self, attrName, logName):
-        """Construct the NAV logger."""
-        StateChangeLogger.__init__(self, logInitial = False,
-                                   excludedStages = self.excludedStages)
-        DelayedChangeMixin.__init__(self)
-
-        self._getLogTimestamp = \
-            lambda state, forced: \
-            DelayedChangeMixin._getLogTimestamp(self, state, forced)
-
-        self._attrName = attrName
-        self._logName = logName
-
-    def _getValue(self, state):
-        """Get the value.
-
-        If both the frequency and the obs settings are available, a tuple
-        containing them is returned, otherwise None."""
-        frequency = getattr(state, self._attrName)
-        obs = getattr(state, self._attrName + "_obs")
-        manual = getattr(state, self._attrName + "_manual")
-        return (frequency, obs, manual)
-
-    def _getMessage(self, flight, state, forced):
-        """Get the message."""
-        (frequency, obs, manual) = self._getValue(state)
-        return None if frequency is None or obs is None or \
-               (not manual and not forced) else \
-               self.getMessage(self._logName, frequency, obs)
-
-    def _isDifferent(self, oldValue, newValue):
-        """Determine if the valie has changed between the given states."""
-        (oldFrequency, oldOBS, _oldManual) = oldValue
-        (newFrequency, newOBS, _newManual) = newValue
-        return oldFrequency!=newFrequency or oldOBS!=newOBS
-
-#---------------------------------------------------------------------------------------
-
-class ILSLogger(NAVLogger):
-    """Logger for the ILS radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(ILSLogger, self).__init__("ils", "ILS")
-
-#---------------------------------------------------------------------------------------
-
-class NAV1Logger(NAVLogger):
-    """Logger for the NAV1 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV1Logger, self).__init__("nav1", "NAV1")
-
-#---------------------------------------------------------------------------------------
-
-class NAV2Logger(NAVLogger):
-    """Logger for the NAV2 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV2Logger, self).__init__("nav2", "NAV2")
-
-#---------------------------------------------------------------------------------------
-
-class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
-    """Base class for the ADF loggers."""
-    def __init__(self, attr, logName):
-        """Construct the ADF logger."""
-        GenericStateChangeLogger.__init__(self, attr,
-                                          "%s: %%s" % (logName,),
-                                          logInitial = False,
-                                          excludedStages =
-                                          NAVLogger.excludedStages,
-                                          minDelay = 3.0, maxDelay = 10.0)
-
-#---------------------------------------------------------------------------------------
-
-class ADF1Logger(ADFLogger):
-    """Logger for the ADF1 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(ADF1Logger, self).__init__("adf1", "ADF1")
-
-#---------------------------------------------------------------------------------------
-
-class ADF2Logger(ADFLogger):
-    """Logger for the ADF2 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(ADF2Logger, self).__init__("adf2", "ADF2")
-
-#---------------------------------------------------------------------------------------
-
-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, DelayedChangeMixin):
-    """Base class for the loggers of the various lights."""
-    def __init__(self, attrName, template):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self)
-        SingleValueMixin.__init__(self, attrName)
-        DelayedChangeMixin.__init__(self)
-        self._getLogTimestamp = \
-            lambda state, forced: \
-            DelayedChangeMixin._getLogTimestamp(self, state, forced)
-
-        self._template = template
-
-    def _getMessage(self, flight, state, forced):
-        """Get the message from the given state."""
-        value = self._getValue(state)
-        if value is None:
-            return None
-        else:
-            return self._template % ("ON" if value 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, DelayedChangeMixin):
-    """Logger for the flaps setting."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True,
-                                   excludedStages =
-                                   [const.STAGE_BOARDING,
-                                    const.STAGE_PUSHANDTAXI,
-                                    const.STAGE_RTO,
-                                    const.STAGE_TAKEOFF])
-        SingleValueMixin.__init__(self, "flapsSet")
-        DelayedChangeMixin.__init__(self)
-        self._getLogTimestamp = \
-            lambda state, forced: \
-            DelayedChangeMixin._getLogTimestamp(self, state, forced)
-
-    def _getMessage(self, flight, state, forced):
-        """Get the message to log on a change."""
-        logState = self._lastChangeState if \
-                   self._lastChangeState is not None else state
-        speed = logState.groundSpeed if logState.groundSpeed<80.0 \
-                else logState.ias
-        message = "Flaps %.0f - %.0f %s, %.0f %s AGL, %.0f ft AMSL" % \
-               (logState.flapsSet, flight.speedFromKnots(speed),
-                flight.getEnglishSpeedUnit(),
-                flight.aglFromFeet(logState.radioAltitude),
-                flight.getEnglishAGLUnit(),
-                logState.altitude)
-
-        return message
-
-#---------------------------------------------------------------------------------------
-
-class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Logger for the gears state."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "gearControlDown")
-
-    def _getMessage(self, flight, state, forced):
-        """Get the message to log on a change."""
-        return "Gears SET to %s at %.0f %s, %.0f ft, %.0f %s AGL" % \
-            ("DOWN" if state.gearControlDown else "UP",
-             flight.speedFromKnots(state.ias),
-             flight.getEnglishSpeedUnit(),
-             state.altitude,
-             flight.aglFromFeet(state.radioAltitude),
-             flight.getEnglishAGLUnit())
-
-#---------------------------------------------------------------------------------------
-
-class APLogger(StateChangeLogger, DelayedChangeMixin):
-    """Log the state of the autopilot."""
-    @staticmethod
-    def _logBoolean(logger, timestamp, what, value):
-        """Log a boolean value.
-
-        what is the name of the value that is being logged."""
-        message = what + " "
-        if value is None:
-            message += "cannot be detected, will not log"
-        else:
-            message += "is " + ("ON" if value else "OFF")
-        logger.message(timestamp, message)
-
-    @staticmethod
-    def _logNumeric(logger, timestamp, what, format, value):
-        """Log a numerical value."""
-        message = what
-        if value is None:
-            message += u" cannot be detected, will not log"
-        else:
-            message += u": " + format % (value,)
-        logger.message(timestamp, message)
-
-    @staticmethod
-    def _logAPMaster(logger, timestamp, state):
-        """Log the AP master state."""
-        APLogger._logBoolean(logger, timestamp, "AP master",
-                             state.apMaster)
-
-    @staticmethod
-    def _logAPHeadingHold(logger, timestamp, state):
-        """Log the AP heading hold state."""
-        APLogger._logBoolean(logger, timestamp, "AP heading hold",
-                             state.apHeadingHold)
-
-    @staticmethod
-    def _logAPHeading(logger, timestamp, state):
-        """Log the AP heading."""
-        APLogger._logNumeric(logger, timestamp, u"AP heading",
-                             u"%03.0f\u00b0", state.apHeading)
-
-    @staticmethod
-    def _logAPAltitudeHold(logger, timestamp, state):
-        """Log the AP altitude hold state."""
-        APLogger._logBoolean(logger, timestamp, "AP altitude hold",
-                             state.apAltitudeHold)
-
-    @staticmethod
-    def _logAPAltitude(logger, timestamp, state):
-        """Log the AP heading."""
-        APLogger._logNumeric(logger, timestamp, u"AP altitude",
-                             u"%.0f ft", state.apAltitude)
-
-    def __init__(self):
-        """Construct the state logger."""
-        StateChangeLogger.__init__(self)
-        DelayedChangeMixin.__init__(self)
-        self._lastLoggedState = None
-        self.logState = lambda flight, logger, state:\
-            APLogger.logState(self, flight, logger, state)
-
-    def _getValue(self, state):
-        """Convert the relevant values from the given state into a tuple."""
-        return (state.apMaster,
-                state.apHeadingHold, state.apHeading,
-                state.apAltitudeHold, state.apAltitude)
-
-    def _isDifferent(self, oldValue, newValue):
-        """Determine if the given old and new values are different (enough) to
-        be logged."""
-        (oldAPMaster, oldAPHeadingHold, oldAPHeading,
-         oldAPAltitudeHold, oldAPAltitude) = oldValue
-        (apMaster, apHeadingHold, apHeading,
-         apAltitudeHold, apAltitude) = newValue
-
-        if apMaster is not None and apMaster!=oldAPMaster:
-            return True
-        if apMaster is False:
-            return False
-
-        if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
-            return True
-        if apHeadingHold is not False and apHeading is not None and \
-            apHeading!=oldAPHeading:
-            return True
-
-        if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
-            return True
-        if apAltitudeHold is not False and apAltitude is not None and \
-            apAltitude!=oldAPAltitude:
-            return True
-
-        return False
-
-    def logState(self, flight, logger, state):
-        """Log the autopilot state."""
-        timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
-        if self._lastLoggedState is None:
-            self._logAPMaster(logger, timestamp, state)
-            if state.apMaster is not False or state.apHeadingHold is None:
-                self._logAPHeadingHold(logger, timestamp, state)
-            if state.apMaster is not False and \
-               (state.apHeadingHold is not False or state.apHeading is None):
-                self._logAPHeading(logger, timestamp, state)
-            if state.apMaster is not False or state.apAltitudeHold is None:
-                self._logAPAltitudeHold(logger, timestamp, state)
-            if state.apMaster is not False and \
-               (state.apAltitudeHold is not False or state.apAltitude is None):
-                self._logAPAltitude(logger, timestamp, state)
-            self._firstCall = False
-        else:
-            oldState = self._lastLoggedState
-
-            apMasterTurnedOn = False
-            if state.apMaster is not None and state.apMaster!=oldState.apMaster:
-                apMasterTurnedOn = state.apMaster
-                self._logAPMaster(logger, timestamp, state)
-
-            if state.apMaster is not False:
-                apHeadingHoldTurnedOn = False
-                if state.apHeadingHold is not None and \
-                   (state.apHeadingHold!=oldState.apHeadingHold or
-                    apMasterTurnedOn):
-                    apHeadingHoldTurnedOn = state.apHeadingHold
-                    self._logAPHeadingHold(logger, timestamp, state)
-
-                if state.apHeadingHold is not False and \
-                   state.apHeading is not None and \
-                   (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
-                    apHeadingHoldTurnedOn):
-                    self._logAPHeading(logger, timestamp, state)
-
-                apAltitudeHoldTurnedOn = False
-                if state.apAltitudeHold is not None and \
-                   (state.apAltitudeHold!=oldState.apAltitudeHold or
-                    apMasterTurnedOn):
-                    apAltitudeHoldTurnedOn = state.apAltitudeHold
-                    self._logAPAltitudeHold(logger, timestamp, state)
-
-                if state.apAltitudeHold is not False and \
-                   state.apAltitude is not None and \
-                   (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
-                    apAltitudeHoldTurnedOn):
-                    self._logAPAltitude(logger, timestamp, state)
-
-        self._lastLoggedState = state
-
-#---------------------------------------------------------------------------------------
-
-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."""
-        return (not flight.config.usingFS2Crew or not state.parking or
-                flight.stage!=const.STAGE_TAXIAFTERLAND) and \
-                state.antiCollisionLightsOn is False and \
-                self.isEngineCondition(state)
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Anti-collision lights were %s" %
-                                                      ("on" if state.antiCollisionLightsOn else "off",)),
-                           1)
-
-    def isEngineCondition(self, state):
-        """Determine if the engines are in such a state that the lights should
-        be on."""
-        if state.n1 is not None:
-            return max(state.n1)>5
-        elif state.rpm is not None:
-            return max(state.rpm)>0
-        else:
-            return False
-
-#---------------------------------------------------------------------------------------
-
-class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
-    """Check for the anti-collision light for Tuplev planes."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        numEnginesRunning = 0
-        for n1 in state.n1:
-            if n1>5: numEnginesRunning += 1
-
-        if flight.stage==const.STAGE_PARKING or \
-           flight.stage==const.STAGE_TAXIAFTERLAND:
-            return numEnginesRunning<len(state.n1) \
-                   and state.antiCollisionLightsOn
-        else:
-            return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
-                   numEnginesRunning<1 and state.antiCollisionLightsOn
-
-#---------------------------------------------------------------------------------------
-
-class TupolevLandingLightsChecker(PatientFaultChecker):
-    """Check if the landing light is not switched on above an IAS of 340 km/h."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.landingLightsOn and state.ias>(340.0*const.KMPHTOKNOTS)
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(TupolevLandingLightsChecker, state.timestamp,
-                           "The landing lights were on above an IAS of 340 km/h",
-                           1)
-
-#---------------------------------------------------------------------------------------
-
-class BankChecker(SimpleFaultChecker):
-    """Check for the bank is within limits."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if flight.stage==const.STAGE_CRUISE:
-            isDH8DXplane = flight.aircraftType==const.AIRCRAFT_DH8D and \
-                           (flight.fsType==const.SIM_XPLANE10 or
-                            flight.fsType==const.SIM_XPLANE9)
-            bankLimit = 35 if isDH8DXplane else 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."""
-        message = "Bank too steep (%.1f)" % (state.bank,)
-        flight.handleFault(BankChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, message),
-                           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 and
-            aircraft.type!=const.AIRCRAFT_F70) 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.smoothedIAS>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.smoothedIAS>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 not state.onTheGround 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 state.landingLightsOn is not None and \
-               ((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 and
-                 state.ias>50.0) 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 in [const.STAGE_TAKEOFF,
-                                      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 TransponderChecker(PatientFaultChecker):
-    """Check if the transponder is used properly."""
-    def __init__(self):
-        """Construct the transponder checker."""
-        super(TransponderChecker, self).__init__()
-        self._liftOffTime = None
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if state.onTheGround:
-            self._liftOffTime = None
-        elif self._liftOffTime is None:
-            self._liftOffTime = state.timestamp
-
-        return state.xpdrC is not None and \
-               ((state.xpdrC and flight.stage in
-                 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
-                (not state.xpdrC and
-                 (flight.stage in
-                  [const.STAGE_CRUISE, const.STAGE_DESCENT,
-                   const.STAGE_GOAROUND] or \
-                  (flight.stage==const.STAGE_LANDING and
-                   state.groundSpeed>50.0) or \
-                  ((not state.autoXPDR or \
-                    (self._liftOffTime is not None and
-                     state.timestamp > (self._liftOffTime+8))) and \
-                   ((flight.stage==const.STAGE_TAKEOFF and
-                     not state.onTheGround) or flight.stage==const.STAGE_CLIMB))
-                  )
-                 )
-                )
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 0
-        message = "Transponder was %s" % \
-                  (("mode C" if state.xpdrC else "standby"),)
-        flight.handleFault(TransponderChecker, 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 and \
-                               not flight.entranceExam 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 and \
-                             not flight.entranceExam 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 if not flight.entranceExam else None
-
-    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 \
-               state.navLightsOn is False
-
-    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 WindSensitiveFaultChecker(FaultChecker):
-    """A fault checker which checks for a fault condition that might arise due
-    to suddenly changing winds.
-
-    If the condition is detected, its fact is logged, and the winds are also
-    logged repeatedly until and for a while after the condition has ceased. A
-    fault is logged only if the condition holds for a certain period of
-    time."""
-    # The time the winds were logged last. This is global to avoid duplicate
-    # logging if several wind-sensitive fault conditions gold
-    _windsLastLogged = None
-
-    # The wind logging interval
-    _windsLogInterval = 5.0
-
-    def __init__(self, faultTimeout = 30.0, afterLoggingTime = 20.0):
-        """Construct the fault checker."""
-        self._faultTimeout = faultTimeout
-        self._afterLoggingTime = afterLoggingTime
-
-        self._faultStarted = None
-        self._faultCeased = None
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check for the condition and do whatever is needed."""
-        timestamp = state.timestamp
-        logWinds = False
-
-        if self.isCondition(flight, aircraft, oldState, state):
-            logWinds = True
-            if self._faultStarted is None:
-                self._faultStarted = timestamp
-                self._faultCeased = None
-                self.logCondition(flight, aircraft, logger, oldState, state,
-                                  False)
-
-                WindSensitiveFaultChecker._windsLastLogged = None
-
-            elif timestamp >= (self._faultStarted + self._faultTimeout):
-                self.logCondition(flight, aircraft, logger, oldState, state,
-                                  True)
-                self._faultStarted = timestamp
-
-        else:
-            if self._faultStarted is not None and self._faultCeased is None:
-                self._faultCeased = timestamp
-                self._faultStarted = None
-
-            if self._faultCeased is not None:
-                if timestamp < (self._faultCeased + self._afterLoggingTime):
-                    logWinds = True
-                else:
-                    self._faultCeased = None
-
-        if logWinds and \
-           (WindSensitiveFaultChecker._windsLastLogged is None or
-            timestamp >= (WindSensitiveFaultChecker._windsLastLogged +
-                          self._windsLogInterval)):
-            logger.message(timestamp, "Winds: %.f knots from %.f" %
-                           (state.windSpeed, state.windDirection))
-            WindSensitiveFaultChecker._windsLastLogged = timestamp
-
-#---------------------------------------------------------------------------------------
-
-class OverspeedChecker(WindSensitiveFaultChecker):
-    """Check if Vne has been exceeded."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.overspeed
-
-    def logCondition(self, flight, aircraft, logger, oldState, state, isFault):
-        """Log the condition fault."""
-        if isFault:
-            flight.handleFault(OverspeedChecker, state.timestamp,
-                               FaultChecker._appendDuring(flight, "Overspeed"),
-                               20)
-        else:
-            logger.message(state.timestamp,
-                           FaultChecker._appendDuring(flight, "Overspeed"))
-
-#---------------------------------------------------------------------------------------
-
-class StallChecker(WindSensitiveFaultChecker):
-    """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 logCondition(self, flight, aircraft, logger, oldState, state, isFault):
-        """Log the condition."""
-        if isFault:
-            score = 40 if flight.stage in [const.STAGE_TAKEOFF,
-                                           const.STAGE_LANDING] else 30
-            flight.handleFault(StallChecker, state.timestamp,
-                               FaultChecker._appendDuring(flight, "Stalled"),
-                               score)
-        else:
-            logger.message(state.timestamp,
-                            FaultChecker._appendDuring(flight, "Stalled"))
-
-#---------------------------------------------------------------------------------------
-
-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 not flight.entranceExam and \
-               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 state.pitotHeatOn is False
-
-    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 ReverserLogger(StateChecker):
-    """Logger for the reverser."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if oldState is not None and state.reverser != oldState.reverser:
-            reverser = max(state.reverser)
-            logger.message(state.timestamp,
-                           "Reverser " + ("unlocked" if reverser else "closed") +
-                           (" - %.0f %s" % (flight.speedFromKnots(state.ias),
-                                            flight.getEnglishSpeedUnit())))
-
-#---------------------------------------------------------------------------------------
-
-class ReverserChecker(SimpleFaultChecker):
-    """Check if the reverser is not used below the speed prescribed for the
-    aircraft."""
-    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.reverser and \
-            state.ias<aircraft.reverseMinSpeed and max(state.reverser)
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        message = "Reverser used below %.0f %s" % \
-                  (flight.speedFromKnots(aircraft.reverseMinSpeed), flight.getEnglishSpeedUnit())
-        flight.handleFault(ReverserChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight, message),
-                           15)
-
-#---------------------------------------------------------------------------------------
-
-class SpeedChecker(StateChecker):
-    """Checker for the ground speed of aircraft.
-
-    If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state is
-    saved as a provisional takeoff state. If the speed then decreases below 50
-    knots, or the plane remains on the ground for more than 40 seconds, a taxi
-    speed error is logged with the highest ground speed detected. This state is
-    also stored in the flight object as a possible
-
-    If the plane becomes airborne within 40 seconds, the stage becomes TAKEOFF,
-    and the previously saved takeoff state is logged.
-
-    During the TAXIAFTERLAND stage, if the speed goes above 50 knots, a fault
-    is logged based on the actual speed."""
-    @staticmethod
-    def logSpeedFault(flight, state, stage = None, updateID = None):
-        """Log the speed fault."""
-        message = "Taxi speed over %.0f %s" % \
-                  (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
-        if stage is None:
-            stage = flight.stage
-        score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
-        return flight.handleFault((SpeedChecker, stage), state.timestamp,
-                                  FaultChecker._appendDuring(flight, message),
-                                  score, updatePrevious = updateID is None,
-                                  updateID = updateID)
-
-    def __init__(self):
-        """Initialize the speed checker."""
-        self._takeoffState = None
-        self._highestSpeedState = None
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check the state as described above."""
-        if flight.stage==const.STAGE_PUSHANDTAXI or \
-           flight.stage==const.STAGE_RTO:
-            if state.groundSpeed>50 and aircraft.hasStrobeLight and \
-               state.strobeLightsOn is False:
-                self.logSpeedFault(flight, state)
-            else:
-                self._checkPushAndTaxi(flight, aircraft, state)
-        elif flight.stage==const.STAGE_TAXIAFTERLAND:
-            if state.groundSpeed>50:
-                self.logSpeedFault(flight, state)
-        else:
-            self._takeoffState = None
-
-    def _checkPushAndTaxi(self, flight, aircraft, state):
-        """Check the speed during the push and taxi stage."""
-        if state.groundSpeed>50:
-            if self._takeoffState is None:
-                self._takeoffState = state
-                self._highestSpeedState = state
-            else:
-                if state.groundSpeed>self._highestSpeedState.groundSpeed:
-                    self._highestSpeedState = state
-
-                if not state.onTheGround:
-                    aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
-                    self._takeoffState = None
-                elif state.timestamp > (self._takeoffState.timestamp + 60):
-                    flight.setRTOState(self._highestSpeedState)
-        elif self._takeoffState is not None:
-            flight.setRTOState(self._highestSpeedState)
-            self._takeoffState = None
-
-#---------------------------------------------------------------------------------------
-
-class StrobeLightsChecker(PatientFaultChecker):
-    """Check if the strobe lights are used properly."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.strobeLightsOn is not None and \
-              ((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 \
-               state.n1 is not None and max(state.n1)>97
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        flight.handleFault(ThrustChecker, state.timestamp,
-                           FaultChecker._appendDuring(flight,
-                                                      "Thrust setting was too high (>97%)"),
-                           FaultChecker._getLinearScore(97, 110, 0, 10,
-                                                        max(state.n1)),
-                           updatePrevious = True)
-
-#---------------------------------------------------------------------------------------
-
-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.smoothedVS
-        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.smoothedVS
-
-        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/common.py
===================================================================
--- src/mlx/common.py	(revision 651)
+++ src/mlx/common.py	(revision 651)
@@ -0,0 +1,60 @@
+import os
+
+#-----------------------------------------------------------------------------
+
+## @package mlx.common
+#
+# Common definitions to be used by both the GUI and possible other parts
+#
+# The main purpose of this module is to import Gtk+2 or 3 and related modules
+# and provide common names for them. There is a similator module in the GUI
+# part (\ref mlx.gui.common), which contains the GUI-specific names. This one
+# contains only what is used by other modules as well.
+# The variable \c pygobject tells which version of Gtk is being used. If it is
+# \c True, Gtk+ 3 is used via the PyGObject interface. Otherwise Gtk+ 2 is
+# used, which is the default on Windows or when the \c FORCE_PYGTK environment
+# variable is set.
+
+#-------------------------------------------------------------------------------
+
+# Due to CEF, PyGTK is the default
+if "FORCE_PYGOBJECT" not in os.environ:
+    print "Using PyGTK"
+    pygobject = False
+    import gobject
+    try:
+        import pygst
+        pygst.require('0.10')
+        import gst
+
+        def gst_init():
+            pass
+
+        def gst_element_factory_make(what):
+            return gst.element_factory_make(what)
+
+        GST_STATE_PLAYING=gst.STATE_PLAYING
+        GST_MESSAGE_EOS=gst.MESSAGE_EOS
+    except:
+        pass
+else:
+    print "Using PyGObject"
+    pygobject = True
+
+    from gi.repository import GObject as gobject
+
+    try:
+        from gi.repository import Gst as gst
+
+        def gst_init():
+            gst.init()
+
+        def gst_element_factory_make(what):
+            return gst.ElementFactory.make(what)
+
+        GST_STATE_PLAYING=gst.State.PLAYING
+        GST_MESSAGE_EOS=gst.MessageType.EOS
+    except:
+        import traceback
+        traceback.print_exc()
+        pass
Index: src/mlx/common.py
===================================================================
--- src/mlx/common.py	(revision 651)
+++ 	(revision )
@@ -1,60 +1,0 @@
-import os
-
-#-----------------------------------------------------------------------------
-
-## @package mlx.common
-#
-# Common definitions to be used by both the GUI and possible other parts
-#
-# The main purpose of this module is to import Gtk+2 or 3 and related modules
-# and provide common names for them. There is a similator module in the GUI
-# part (\ref mlx.gui.common), which contains the GUI-specific names. This one
-# contains only what is used by other modules as well.
-# The variable \c pygobject tells which version of Gtk is being used. If it is
-# \c True, Gtk+ 3 is used via the PyGObject interface. Otherwise Gtk+ 2 is
-# used, which is the default on Windows or when the \c FORCE_PYGTK environment
-# variable is set.
-
-#-------------------------------------------------------------------------------
-
-# Due to CEF, PyGTK is the default
-if "FORCE_PYGOBJECT" not in os.environ:
-    print "Using PyGTK"
-    pygobject = False
-    import gobject
-    try:
-        import pygst
-        pygst.require('0.10')
-        import gst
-
-        def gst_init():
-            pass
-
-        def gst_element_factory_make(what):
-            return gst.element_factory_make(what)
-
-        GST_STATE_PLAYING=gst.STATE_PLAYING
-        GST_MESSAGE_EOS=gst.MESSAGE_EOS
-    except:
-        pass
-else:
-    print "Using PyGObject"
-    pygobject = True
-
-    from gi.repository import GObject as gobject
-
-    try:
-        from gi.repository import Gst as gst
-
-        def gst_init():
-            gst.init()
-
-        def gst_element_factory_make(what):
-            return gst.ElementFactory.make(what)
-
-        GST_STATE_PLAYING=gst.State.PLAYING
-        GST_MESSAGE_EOS=gst.MessageType.EOS
-    except:
-        import traceback
-        traceback.print_exc()
-        pass
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 689)
+++ src/mlx/config.py	(revision 689)
@@ -0,0 +1,1021 @@
+# -*- encoding: utf-8 -*-
+
+import const
+from util import secondaryInstallation, utf2unicode
+
+import os
+import sys
+import traceback
+import ConfigParser
+
+## @package mlx.config
+#
+# The handling of the configuration.
+#
+# The \ref Config class contains the main configuration and is capable of
+# loading and saving the configuration. It contains getters and setters for the
+# configuration options.
+#
+# Some parts of the configuration are not simple data items, like strings or
+# booleans, but more complicated data. These have their own class, like \ref
+# ApproachCallouts or \ref Checklist.
+
+#-------------------------------------------------------------------------------
+
+configPath = os.path.join(os.path.expanduser("~"),
+                          "mlx.config" if os.name=="nt" else ".mlxrc") + \
+                          ("-secondary" if secondaryInstallation else "")
+
+#-------------------------------------------------------------------------------
+
+if os.name=="nt":
+    _languageMap = { "en_GB" : "eng",
+                     "hu_HU" : "hun" }
+
+#-------------------------------------------------------------------------------
+
+class Hotkey(object):
+    """A hotkey."""
+    def __init__(self, ctrl = False, shift = False, key = "0"):
+        """Construct the hotkey."""
+        self.ctrl = ctrl
+        self.shift = shift
+        self.key = key
+
+    def set(self, s):
+        """Set the hotkey from the given string."""
+        self.ctrl = "C" in s[:-1]
+        self.shift = "S" in s[:-1]
+        self.key = s[-1]
+
+    def __eq__(self, other):
+        """Check if the given hotkey is equal to the other one."""
+        return self.ctrl == other.ctrl and self.shift == other.shift and \
+               self.key == other.key
+
+    def __ne__(self, other):
+        """Check if the given hotkey is not equal to the other one."""
+        return not self==other
+
+    def __str__(self):
+        """Construct the hotkey to a string."""
+        s = ""
+        if self.ctrl: s += "C"
+        if self.shift: s += "S"
+        s += self.key
+        return s
+
+#-------------------------------------------------------------------------------
+
+class Checklist(object):
+    """A checklist for a certain aircraft type."""
+    # The name of the section of the checklists
+    SECTION="checklists"
+
+    @staticmethod
+    def fromConfig(config, aircraftType):
+        """Create a checklist for the given aircraft type from the given
+        config."""
+        baseName = "checklist." + const.icaoCodes[aircraftType] + "."
+        fileList = []
+        while True:
+            option = baseName + str(len(fileList))
+            if config.has_option(Checklist.SECTION, option):
+                fileList.append(config.get(Checklist.SECTION, option))
+            else:
+                break
+
+        return Checklist(fileList)
+
+    def __init__(self, fileList = None):
+        """Construct the check list with the given file list."""
+        self._fileList = [] if fileList is None else fileList[:]
+
+    def clone(self):
+        """Clone the checklist."""
+        return Checklist(self._fileList)
+
+    def toConfig(self, config, aircraftType):
+        """Add this checklist to the given config."""
+        baseName = "checklist." + const.icaoCodes[aircraftType] + "."
+        for index in range(0, len(self._fileList)):
+            option = baseName + str(index)
+            config.set(Checklist.SECTION, option,
+                       self._fileList[index])
+
+    def __eq__(self, other):
+        """Determine if the checklist is equal to the given other one."""
+        return self._fileList == other._fileList
+
+    def __ne__(self, other):
+        """Determine if the checklist is not equal to the given other one."""
+        return not self==other
+
+    def __len__(self):
+        """Get the length of the file list."""
+        return len(self._fileList)
+
+    def __getitem__(self, index):
+        """Get the file with the given index."""
+        return self._fileList[index]
+
+    def __iter__(self):
+        """Iterate over the files."""
+        return iter(self._fileList)
+
+#-------------------------------------------------------------------------------
+
+class ApproachCallouts(object):
+    """The approach callouts for a certain aircraft type."""
+    # The name of the section of the approach callouts
+    SECTION="callouts"
+
+    @staticmethod
+    def fromConfig(config, aircraftType):
+        """Create a checklist for the given aircraft type from the given
+        config."""
+        baseName = "callouts." + const.icaoCodes[aircraftType] + "."
+        mapping = {}
+        while True:
+            option = baseName + str(len(mapping))
+            if config.has_option(ApproachCallouts.SECTION, option):
+                value = config.get(ApproachCallouts.SECTION, option)
+                (altitude, path) = value.split(",")
+                altitude = int(altitude.strip())
+                path = path.strip()
+                mapping[altitude] = path
+            else:
+                break
+
+        return ApproachCallouts(mapping)
+
+    def __init__(self, mapping = None):
+        """Construct the check list with the given mapping of altitudes to
+        files."""
+        self._mapping = {} if mapping is None else mapping.copy()
+
+    def clone(self):
+        """Clone the callout information."""
+        return ApproachCallouts(self._mapping)
+
+    def toConfig(self, config, aircraftType):
+        """Add this checklist to the given config."""
+        baseName = "callouts." + const.icaoCodes[aircraftType] + "."
+        index = 0
+        for (altitude, path) in self._mapping.iteritems():
+            option = baseName + str(index)
+            config.set(ApproachCallouts.SECTION, option,
+                       "%d, %s" % (altitude, path))
+            index += 1
+
+    def getAltitudes(self, descending = True):
+        """Get the altitudes in decreasing order by default."""
+        altitudes = self._mapping.keys()
+        altitudes.sort(reverse = descending)
+        return altitudes
+
+    def __nonzero__(self):
+        """Return if there is anything in the mapping."""
+        return not not self._mapping
+
+    def __eq__(self, other):
+        """Determine if the approach callout mapping is equal to the given
+        other one."""
+        return self._mapping == other._mapping
+
+    def __ne__(self, other):
+        """Determine if the approach callout mapping is not equal to the given
+        other one."""
+        return not self==other
+
+    def __len__(self):
+        """Get the number of elements in the mapping."""
+        return len(self._mapping)
+
+    def __getitem__(self, altitude):
+        """Get the file that is associated with the given altitude.
+
+        If no such file found, return None."""
+        return self._mapping[altitude] if altitude in self._mapping else None
+
+    def __iter__(self):
+        """Iterate over the pairs of altitudes and paths in decreasing order of
+        the altitude."""
+        altitudes = self.getAltitudes()
+
+        for altitude in altitudes:
+            yield (altitude, self._mapping[altitude])
+
+#-------------------------------------------------------------------------------
+
+class Config(object):
+    """Our configuration."""
+    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update/cef"
+
+    _messageTypesSection = "messageTypes"
+
+    def __init__(self):
+        """Construct the configuration with default values."""
+
+        self._pilotID = ""
+        self._password = ""
+        self._rememberPassword = False
+
+        self._language = ""
+        self._hideMinimizedWindow = True
+        self._quitOnClose = False
+        self._onlineGateSystem = not secondaryInstallation
+        self._onlineACARS = not secondaryInstallation
+        self._flareTimeFromFS = False
+        self._syncFSTime = False
+        self._usingFS2Crew = False
+        self._iasSmoothingLength = -2
+        self._vsSmoothingLength = -2
+
+        self._useSimBrief = False
+        self._simBriefUserName = ""
+        self._simBriefPassword = ""
+        self._rememberSimBriefPassword = False
+
+        self._pirepDirectory = None
+        self._pirepAutoSave = False
+
+        self._defaultMSFS = os.name=="nt"
+
+        self._enableSounds = not secondaryInstallation
+
+        self._pilotControlsSounds = True
+        self._pilotHotkey = Hotkey(ctrl = True, shift = False, key = "0")
+
+        self._enableApproachCallouts = False
+        self._speedbrakeAtTD = True
+
+        self._enableChecklists = False
+        self._checklistHotkey = Hotkey(ctrl = True, shift = True, key = "0")
+
+        self._autoUpdate = True
+        self._updateURL = Config.DEFAULT_UPDATE_URL
+        if secondaryInstallation:
+            self._updateURL += "/exp"
+
+        self._messageTypeLevels = {}
+
+        self._checklists = {}
+        self._approachCallouts = {}
+        for aircraftType in const.aircraftTypes:
+            self._checklists[aircraftType] = Checklist()
+            self._approachCallouts[aircraftType] = ApproachCallouts()
+
+        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 quitOnClose(self):
+        """Get whether the application should quit when the close button is
+        clicked."""
+        return self._quitOnClose
+
+    @quitOnClose.setter
+    def quitOnClose(self, quitOnClose):
+        """Set whether the application should quit when the close button is
+        clicked."""
+        if quitOnClose!=self._quitOnClose:
+            self._quitOnClose = quitOnClose
+            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 usingFS2Crew(self):
+        """Get whether the FS2Crew addon is being used."""
+        return self._usingFS2Crew
+
+    @usingFS2Crew.setter
+    def usingFS2Crew(self, usingFS2Crew):
+        """Set whether the FS2Crew addon is being used."""
+        if usingFS2Crew!=self._usingFS2Crew:
+            self._usingFS2Crew = usingFS2Crew
+            self._modified = True
+
+    @property
+    def iasSmoothingLength(self):
+        """Get the number of samples over which the IAS is averaged for the
+        smoothed IAS calculation. It may be negative, in which case smoothing
+        is disabled, but we nevertheless store the number of seconds in case it
+        may become useful later."""
+        return self._iasSmoothingLength
+
+    @property
+    def realIASSmoothingLength(self):
+        """Get the real smoothing length of IAS."""
+        return max(self._iasSmoothingLength, 1)
+
+    @iasSmoothingLength.setter
+    def iasSmoothingLength(self, iasSmoothingLength):
+        """Set the number of samples over which the IAS is averaged for the
+        smoothed IAS calculation."""
+        if iasSmoothingLength!=self._iasSmoothingLength:
+            self._iasSmoothingLength = iasSmoothingLength
+            self._modified = True
+
+    @property
+    def vsSmoothingLength(self):
+        """Get the number of samples over which the VS is averaged for the
+        smoothed VS calculation. It may be negative, in which case smoothing
+        is disabled, but we nevertheless store the number of seconds in case it
+        may become useful later."""
+        return self._vsSmoothingLength
+
+    @property
+    def realVSSmoothingLength(self):
+        """Get the real smoothing length of VS."""
+        return max(self._vsSmoothingLength, 1)
+
+    @vsSmoothingLength.setter
+    def vsSmoothingLength(self, vsSmoothingLength):
+        """Set the number of samples over which the VS is averaged for the
+        smoothed VS calculation."""
+        if vsSmoothingLength!=self._vsSmoothingLength:
+            self._vsSmoothingLength = vsSmoothingLength
+            self._modified = True
+
+    @property
+    def useSimBrief(self):
+        """Check if SimBrief should be used."""
+        return self._useSimBrief
+
+    @useSimBrief.setter
+    def useSimBrief(self, useSimBrief):
+        """Check if SimBrief should be used."""
+        if self._useSimBrief != useSimBrief:
+            self._useSimBrief = useSimBrief
+            self._modified = True
+
+    @property
+    def simBriefUserName(self):
+        """Get the SimBrief user name last used"""
+        return self._simBriefUserName
+
+    @simBriefUserName.setter
+    def simBriefUserName(self, simBriefUserName):
+        """Set the SimBrief user name to be used next."""
+        if self._simBriefUserName != simBriefUserName:
+            self._simBriefUserName = simBriefUserName
+            self._modified = True
+
+    @property
+    def simBriefPassword(self):
+        """Get the SimBrief password last used"""
+        return self._simBriefPassword
+
+    @simBriefPassword.setter
+    def simBriefPassword(self, simBriefPassword):
+        """Set the SimBrief password to be used next."""
+        if self._simBriefPassword != simBriefPassword:
+            self._simBriefPassword = simBriefPassword
+            self._modified = True
+
+    @property
+    def rememberSimBriefPassword(self):
+        """Get if we should remember the SimBrief password."""
+        return self._rememberSimBriefPassword
+
+    @rememberSimBriefPassword.setter
+    def rememberSimBriefPassword(self, rememberSimBriefPassword):
+        """Set if we should remember the SimBrief password."""
+        if rememberSimBriefPassword!=self._rememberSimBriefPassword:
+            self._rememberSimBriefPassword = rememberSimBriefPassword
+            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
+            if self._pirepDirectory is None:
+                self._pirepAutoSave = False
+            self._modified = True
+
+    @property
+    def pirepAutoSave(self):
+        """Get whether the PIREP should be saved automatically when it becomes
+        saveable."""
+        return self._pirepAutoSave
+
+    @pirepAutoSave.setter
+    def pirepAutoSave(self, pirepAutoSave):
+        """Set whether the PIREP should be saved automatically when it becomes
+        saveable."""
+        pirepAutoSave = pirepAutoSave and self._pirepDirectory is not None
+        if pirepAutoSave!=self._pirepAutoSave:
+            self._pirepAutoSave = pirepAutoSave
+            self._modified = True
+
+    @property
+    def defaultMSFS(self):
+        """Get if the default simulator type is MS FS."""
+        return self._defaultMSFS
+
+    @defaultMSFS.setter
+    def defaultMSFS(self, defaultMSFS):
+        """Set if the default simulator type is MS FS."""
+        if defaultMSFS!=self._defaultMSFS:
+            self._defaultMSFS = defaultMSFS
+            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 enableSounds(self):
+        """Get whether background sounds are enabled."""
+        return self._enableSounds
+
+    @enableSounds.setter
+    def enableSounds(self, enableSounds):
+        """Set whether background sounds are enabled."""
+        if enableSounds!=self._enableSounds:
+            self._enableSounds = enableSounds
+            self._modified = True
+
+    @property
+    def pilotControlsSounds(self):
+        """Get whether the pilot controls the background sounds."""
+        return self._pilotControlsSounds
+
+    @pilotControlsSounds.setter
+    def pilotControlsSounds(self, pilotControlsSounds):
+        """Set whether the pilot controls the background sounds."""
+        if pilotControlsSounds!=self._pilotControlsSounds:
+            self._pilotControlsSounds = pilotControlsSounds
+            self._modified = True
+
+    @property
+    def pilotHotkey(self):
+        """Get the pilot's hotkey."""
+        return self._pilotHotkey
+
+    @pilotHotkey.setter
+    def pilotHotkey(self, pilotHotkey):
+        """Set the pilot's hotkey."""
+        if pilotHotkey!=self._pilotHotkey:
+            self._pilotHotkey = pilotHotkey
+            self._modified = True
+
+    @property
+    def enableApproachCallouts(self):
+        """Get whether the approach callouts should be played."""
+        return self._enableApproachCallouts
+
+    @enableApproachCallouts.setter
+    def enableApproachCallouts(self, enableApproachCallouts):
+        """Set whether the approach callouts should be played."""
+        if enableApproachCallouts!=self._enableApproachCallouts:
+            self._enableApproachCallouts = enableApproachCallouts
+            self._modified = True
+
+    @property
+    def speedbrakeAtTD(self):
+        """Get whether the speedbrake sounds should be played at touchdown."""
+        return self._speedbrakeAtTD
+
+    @speedbrakeAtTD.setter
+    def speedbrakeAtTD(self, speedbrakeAtTD):
+        """Set whether the speedbrake sounds should be played at touchdown."""
+        if speedbrakeAtTD!=self._speedbrakeAtTD:
+            self._speedbrakeAtTD = speedbrakeAtTD
+            self._modified = True
+
+    @property
+    def enableChecklists(self):
+        """Get whether aircraft-specific checklists should be played."""
+        return self._enableChecklists
+
+    @enableChecklists.setter
+    def enableChecklists(self, enableChecklists):
+        """Get whether aircraft-specific checklists should be played."""
+        if enableChecklists!=self._enableChecklists:
+            self._enableChecklists = enableChecklists
+            self._modified = True
+
+    @property
+    def checklistHotkey(self):
+        """Get the checklist hotkey."""
+        return self._checklistHotkey
+
+    @checklistHotkey.setter
+    def checklistHotkey(self, checklistHotkey):
+        """Set the checklist hotkey."""
+        if checklistHotkey!=self._checklistHotkey:
+            self._checklistHotkey = checklistHotkey
+            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 getChecklist(self, aircraftType):
+        """Get the checklist for the given aircraft type."""
+        return self._checklists[aircraftType]
+
+    def setChecklist(self, aircraftType, checklist):
+        """Set the checklist for the given aircraft type."""
+        if checklist!=self._checklists[aircraftType]:
+            self._checklists[aircraftType] = checklist.clone()
+            self._modified = True
+
+    def getApproachCallouts(self, aircraftType):
+        """Get the approach callouts for the given aircraft type."""
+        return self._approachCallouts[aircraftType]
+
+    def setApproachCallouts(self, aircraftType, approachCallouts):
+        """Set the approach callouts for the given aircraft type."""
+        if not approachCallouts==self._approachCallouts[aircraftType]:
+            self._approachCallouts[aircraftType] = approachCallouts.clone()
+            self._modified = True
+
+    def load(self):
+        """Load the configuration from its default location."""
+        try:
+            config = ConfigParser.RawConfigParser()
+            config.read(configPath)
+        except:
+            traceback.print_exc()
+            return
+
+        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._quitOnClose = self._getBoolean(config, "general",
+                                             "quitOnClose", False)
+
+        self._onlineGateSystem = self._getBoolean(config, "general",
+                                                  "onlineGateSystem",
+                                                  not secondaryInstallation)
+        self._onlineACARS = self._getBoolean(config, "general",
+                                             "onlineACARS",
+                                             not secondaryInstallation)
+        self._flareTimeFromFS = self._getBoolean(config, "general",
+                                                 "flareTimeFromFS",
+                                                 False)
+        self._syncFSTime = self._getBoolean(config, "general",
+                                            "syncFSTime",
+                                            False)
+        self._usingFS2Crew = self._getBoolean(config, "general",
+                                              "usingFS2Crew",
+                                              False)
+        self._iasSmoothingLength = int(self._get(config, "general",
+                                                 "iasSmoothingLength",
+                                                 -2))
+        self._vsSmoothingLength = int(self._get(config, "general",
+                                                "vsSmoothingLength",
+                                                -2))
+
+        self._useSimBrief = self._getBoolean(config, "simbrief",
+                                             "use", False)
+        self._simBriefUserName = self._get(config, "simbrief",
+                                           "username", "")
+        self._simBriefPassword = self._get(config, "simbrief",
+                                           "password", "")
+        self._rememberSimBriefPassword = self._getBoolean(config, "simbrief",
+                                                          "rememberPassword",
+                                                          False)
+
+        self._pirepDirectory = self._get(config, "general",
+                                         "pirepDirectory", None)
+
+        self._pirepAutoSave = self._getBoolean(config, "general",
+                                               "pirepAutoSave", False)
+        if self._pirepDirectory is None:
+            self._pirepAutoSave = False
+
+        self._messageTypeLevels = {}
+        for messageType in const.messageTypes:
+            self._messageTypeLevels[messageType] = \
+                self._getMessageTypeLevel(config, messageType)
+
+        self._enableSounds = self._getBoolean(config, "sounds", "enable",
+                                              not secondaryInstallation)
+        self._pilotControlsSounds = self._getBoolean(config, "sounds",
+                                                     "pilotControls", True)
+        self._pilotHotkey.set(self._get(config, "sounds",
+                                        "pilotHotkey", "C0"))
+        self._enableApproachCallouts = \
+            self._getBoolean(config, "sounds", "enableApproachCallouts", False)
+        self._speedbrakeAtTD = self._getBoolean(config, "sounds",
+                                                "speedbrakeAtTD", True)
+
+        self._enableChecklists = self._getBoolean(config, "sounds",
+                                                  "enableChecklists", False)
+        self._checklistHotkey.set(self._get(config, "sounds",
+                                            "checklistHotkey", "CS0"))
+
+        self._autoUpdate = self._getBoolean(config, "update", "auto", True)
+        self._updateURL = self._get(config, "update", "url",
+                                    Config.DEFAULT_UPDATE_URL +
+                                    ("/exp" if secondaryInstallation else ""))
+
+        for aircraftType in const.aircraftTypes:
+            self._checklists[aircraftType] = \
+                Checklist.fromConfig(config, aircraftType)
+            self._approachCallouts[aircraftType] = \
+                ApproachCallouts.fromConfig(config, aircraftType)
+
+        self._defaultMSFS = self._getBoolean(config, "general",
+                                             "defaultMSFS", os.name=="nt")
+
+        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", "quitOnClose",
+                   "yes" if self._quitOnClose 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")
+        config.set("general", "usingFS2Crew",
+                   "yes" if self._usingFS2Crew else "no")
+        config.set("general", "iasSmoothingLength",
+                   str(self._iasSmoothingLength))
+        config.set("general", "vsSmoothingLength",
+                   str(self._vsSmoothingLength))
+
+        config.add_section("simbrief")
+        config.set("simbrief", "use",
+                   "yes" if self._useSimBrief else "no")
+        config.set("simbrief", "username", self._simBriefUserName)
+        config.set("simbrief", "password", self._simBriefPassword)
+        config.set("simbrief", "rememberPassword",
+                   "yes" if self._rememberSimBriefPassword else "no")
+
+        if self._pirepDirectory is not None:
+            config.set("general", "pirepDirectory", self._pirepDirectory)
+        config.set("general", "pirepAutoSave",
+                   "yes" if self._pirepAutoSave else "no")
+
+        config.set("general", "defaultMSFS",
+                   "yes" if self._defaultMSFS else "no")
+
+        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("sounds")
+        config.set("sounds", "enable",
+                   "yes" if self._enableSounds else "no")
+        config.set("sounds", "pilotControls",
+                   "yes" if self._pilotControlsSounds else "no")
+        config.set("sounds", "pilotHotkey", str(self._pilotHotkey))
+        config.set("sounds", "enableApproachCallouts",
+                   "yes" if self._enableApproachCallouts else "no")
+        config.set("sounds", "speedbrakeAtTD",
+                   "yes" if self._speedbrakeAtTD else "no")
+
+        config.set("sounds", "enableChecklists",
+                   "yes" if self._enableChecklists else "no")
+        config.set("sounds", "checklistHotkey",
+                   str(self._checklistHotkey))
+
+        config.add_section("update")
+        config.set("update", "auto",
+                   "yes" if self._autoUpdate else "no")
+        config.set("update", "url", self._updateURL)
+
+        config.add_section(Checklist.SECTION)
+        config.add_section(ApproachCallouts.SECTION)
+        for aircraftType in const.aircraftTypes:
+            self._checklists[aircraftType].toConfig(config, aircraftType)
+            self._approachCallouts[aircraftType].toConfig(config, aircraftType)
+
+        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
+
+            print "Configuration saved:"
+            self.log()
+
+        except Exception, e:
+            print >> sys.stderr, "Failed to update config: " + \
+                                 utf2unicode(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 secondaryInstallation:
+            return const.MESSAGELEVEL_NONE
+        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]
+
+    def log(self):
+        """Log the configuration by printing the values"""
+        print "  pilot ID:", self._pilotID
+        print "  rememberPassword:", self._rememberPassword
+
+        print "  language:", self._language
+
+        print "  hideMinimizedWindow:", self._hideMinimizedWindow
+        print "  quitOnClose:", self._quitOnClose
+
+        print "  onlineGateSystem:", self._onlineGateSystem
+        print "  onlineACARS:", self._onlineACARS
+
+        print "  flareTimeFromFS:", self._flareTimeFromFS
+        print "  syncFSTime:", self._syncFSTime
+        print "  usingFS2Crew:", self._usingFS2Crew
+
+        print "  iasSmoothingLength:", self._iasSmoothingLength
+        print "  vsSmoothingLength:", self._vsSmoothingLength
+
+        print "  useSimBrief:", self._useSimBrief
+        print "  simBriefUserName:", self._simBriefUserName
+        print "  rememberSimBriefPassword:", self._rememberSimBriefPassword
+
+        print "  pirepDirectory:", self._pirepDirectory
+        print "  pirepAutoSave:", self._pirepAutoSave
+
+        print "  defaultMSFS:", self._defaultMSFS
+
+        print "  enableSounds:", self._enableSounds
+
+        print "  pilotControlsSounds:", self._pilotControlsSounds
+        print "  pilotHotkey:", str(self._pilotHotkey)
+
+        print "  enableApproachCallouts:", self._enableApproachCallouts
+        print "  speedbrakeAtTD:", self._speedbrakeAtTD
+
+        print "  enableChecklists:", self._enableChecklists
+        print "  checklistHotkey:", str(self._checklistHotkey)
+
+        print "  autoUpdate:", self._autoUpdate
+        print "  updateURL:", self._updateURL
+
+        print "  messageTypeLevels:"
+        for (type, level) in self._messageTypeLevels.iteritems():
+            print "    %s: %s" % (const.messageType2string(type),
+                                  const.messageLevel2string(level))
+
+        print "  checklists:"
+        for (type, checklist) in self._checklists.iteritems():
+            print "    %s:" % (const.icaoCodes[type],)
+            for path in checklist:
+                print "      " + path
+
+        print "  approachCallouts:"
+        for (type, approachCallouts) in self._approachCallouts.iteritems():
+            print "    %s:" % (const.icaoCodes[type],)
+            for (altitude, path) in approachCallouts:
+                print "      %d: %s" % (altitude, path)
+
+#-------------------------------------------------------------------------------
Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 689)
+++ 	(revision )
@@ -1,1021 +1,0 @@
-# -*- encoding: utf-8 -*-
-
-import const
-from util import secondaryInstallation, utf2unicode
-
-import os
-import sys
-import traceback
-import ConfigParser
-
-## @package mlx.config
-#
-# The handling of the configuration.
-#
-# The \ref Config class contains the main configuration and is capable of
-# loading and saving the configuration. It contains getters and setters for the
-# configuration options.
-#
-# Some parts of the configuration are not simple data items, like strings or
-# booleans, but more complicated data. These have their own class, like \ref
-# ApproachCallouts or \ref Checklist.
-
-#-------------------------------------------------------------------------------
-
-configPath = os.path.join(os.path.expanduser("~"),
-                          "mlx.config" if os.name=="nt" else ".mlxrc") + \
-                          ("-secondary" if secondaryInstallation else "")
-
-#-------------------------------------------------------------------------------
-
-if os.name=="nt":
-    _languageMap = { "en_GB" : "eng",
-                     "hu_HU" : "hun" }
-
-#-------------------------------------------------------------------------------
-
-class Hotkey(object):
-    """A hotkey."""
-    def __init__(self, ctrl = False, shift = False, key = "0"):
-        """Construct the hotkey."""
-        self.ctrl = ctrl
-        self.shift = shift
-        self.key = key
-
-    def set(self, s):
-        """Set the hotkey from the given string."""
-        self.ctrl = "C" in s[:-1]
-        self.shift = "S" in s[:-1]
-        self.key = s[-1]
-
-    def __eq__(self, other):
-        """Check if the given hotkey is equal to the other one."""
-        return self.ctrl == other.ctrl and self.shift == other.shift and \
-               self.key == other.key
-
-    def __ne__(self, other):
-        """Check if the given hotkey is not equal to the other one."""
-        return not self==other
-
-    def __str__(self):
-        """Construct the hotkey to a string."""
-        s = ""
-        if self.ctrl: s += "C"
-        if self.shift: s += "S"
-        s += self.key
-        return s
-
-#-------------------------------------------------------------------------------
-
-class Checklist(object):
-    """A checklist for a certain aircraft type."""
-    # The name of the section of the checklists
-    SECTION="checklists"
-
-    @staticmethod
-    def fromConfig(config, aircraftType):
-        """Create a checklist for the given aircraft type from the given
-        config."""
-        baseName = "checklist." + const.icaoCodes[aircraftType] + "."
-        fileList = []
-        while True:
-            option = baseName + str(len(fileList))
-            if config.has_option(Checklist.SECTION, option):
-                fileList.append(config.get(Checklist.SECTION, option))
-            else:
-                break
-
-        return Checklist(fileList)
-
-    def __init__(self, fileList = None):
-        """Construct the check list with the given file list."""
-        self._fileList = [] if fileList is None else fileList[:]
-
-    def clone(self):
-        """Clone the checklist."""
-        return Checklist(self._fileList)
-
-    def toConfig(self, config, aircraftType):
-        """Add this checklist to the given config."""
-        baseName = "checklist." + const.icaoCodes[aircraftType] + "."
-        for index in range(0, len(self._fileList)):
-            option = baseName + str(index)
-            config.set(Checklist.SECTION, option,
-                       self._fileList[index])
-
-    def __eq__(self, other):
-        """Determine if the checklist is equal to the given other one."""
-        return self._fileList == other._fileList
-
-    def __ne__(self, other):
-        """Determine if the checklist is not equal to the given other one."""
-        return not self==other
-
-    def __len__(self):
-        """Get the length of the file list."""
-        return len(self._fileList)
-
-    def __getitem__(self, index):
-        """Get the file with the given index."""
-        return self._fileList[index]
-
-    def __iter__(self):
-        """Iterate over the files."""
-        return iter(self._fileList)
-
-#-------------------------------------------------------------------------------
-
-class ApproachCallouts(object):
-    """The approach callouts for a certain aircraft type."""
-    # The name of the section of the approach callouts
-    SECTION="callouts"
-
-    @staticmethod
-    def fromConfig(config, aircraftType):
-        """Create a checklist for the given aircraft type from the given
-        config."""
-        baseName = "callouts." + const.icaoCodes[aircraftType] + "."
-        mapping = {}
-        while True:
-            option = baseName + str(len(mapping))
-            if config.has_option(ApproachCallouts.SECTION, option):
-                value = config.get(ApproachCallouts.SECTION, option)
-                (altitude, path) = value.split(",")
-                altitude = int(altitude.strip())
-                path = path.strip()
-                mapping[altitude] = path
-            else:
-                break
-
-        return ApproachCallouts(mapping)
-
-    def __init__(self, mapping = None):
-        """Construct the check list with the given mapping of altitudes to
-        files."""
-        self._mapping = {} if mapping is None else mapping.copy()
-
-    def clone(self):
-        """Clone the callout information."""
-        return ApproachCallouts(self._mapping)
-
-    def toConfig(self, config, aircraftType):
-        """Add this checklist to the given config."""
-        baseName = "callouts." + const.icaoCodes[aircraftType] + "."
-        index = 0
-        for (altitude, path) in self._mapping.iteritems():
-            option = baseName + str(index)
-            config.set(ApproachCallouts.SECTION, option,
-                       "%d, %s" % (altitude, path))
-            index += 1
-
-    def getAltitudes(self, descending = True):
-        """Get the altitudes in decreasing order by default."""
-        altitudes = self._mapping.keys()
-        altitudes.sort(reverse = descending)
-        return altitudes
-
-    def __nonzero__(self):
-        """Return if there is anything in the mapping."""
-        return not not self._mapping
-
-    def __eq__(self, other):
-        """Determine if the approach callout mapping is equal to the given
-        other one."""
-        return self._mapping == other._mapping
-
-    def __ne__(self, other):
-        """Determine if the approach callout mapping is not equal to the given
-        other one."""
-        return not self==other
-
-    def __len__(self):
-        """Get the number of elements in the mapping."""
-        return len(self._mapping)
-
-    def __getitem__(self, altitude):
-        """Get the file that is associated with the given altitude.
-
-        If no such file found, return None."""
-        return self._mapping[altitude] if altitude in self._mapping else None
-
-    def __iter__(self):
-        """Iterate over the pairs of altitudes and paths in decreasing order of
-        the altitude."""
-        altitudes = self.getAltitudes()
-
-        for altitude in altitudes:
-            yield (altitude, self._mapping[altitude])
-
-#-------------------------------------------------------------------------------
-
-class Config(object):
-    """Our configuration."""
-    DEFAULT_UPDATE_URL = "http://mlx.varadiistvan.hu/update/cef"
-
-    _messageTypesSection = "messageTypes"
-
-    def __init__(self):
-        """Construct the configuration with default values."""
-
-        self._pilotID = ""
-        self._password = ""
-        self._rememberPassword = False
-
-        self._language = ""
-        self._hideMinimizedWindow = True
-        self._quitOnClose = False
-        self._onlineGateSystem = not secondaryInstallation
-        self._onlineACARS = not secondaryInstallation
-        self._flareTimeFromFS = False
-        self._syncFSTime = False
-        self._usingFS2Crew = False
-        self._iasSmoothingLength = -2
-        self._vsSmoothingLength = -2
-
-        self._useSimBrief = False
-        self._simBriefUserName = ""
-        self._simBriefPassword = ""
-        self._rememberSimBriefPassword = False
-
-        self._pirepDirectory = None
-        self._pirepAutoSave = False
-
-        self._defaultMSFS = os.name=="nt"
-
-        self._enableSounds = not secondaryInstallation
-
-        self._pilotControlsSounds = True
-        self._pilotHotkey = Hotkey(ctrl = True, shift = False, key = "0")
-
-        self._enableApproachCallouts = False
-        self._speedbrakeAtTD = True
-
-        self._enableChecklists = False
-        self._checklistHotkey = Hotkey(ctrl = True, shift = True, key = "0")
-
-        self._autoUpdate = True
-        self._updateURL = Config.DEFAULT_UPDATE_URL
-        if secondaryInstallation:
-            self._updateURL += "/exp"
-
-        self._messageTypeLevels = {}
-
-        self._checklists = {}
-        self._approachCallouts = {}
-        for aircraftType in const.aircraftTypes:
-            self._checklists[aircraftType] = Checklist()
-            self._approachCallouts[aircraftType] = ApproachCallouts()
-
-        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 quitOnClose(self):
-        """Get whether the application should quit when the close button is
-        clicked."""
-        return self._quitOnClose
-
-    @quitOnClose.setter
-    def quitOnClose(self, quitOnClose):
-        """Set whether the application should quit when the close button is
-        clicked."""
-        if quitOnClose!=self._quitOnClose:
-            self._quitOnClose = quitOnClose
-            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 usingFS2Crew(self):
-        """Get whether the FS2Crew addon is being used."""
-        return self._usingFS2Crew
-
-    @usingFS2Crew.setter
-    def usingFS2Crew(self, usingFS2Crew):
-        """Set whether the FS2Crew addon is being used."""
-        if usingFS2Crew!=self._usingFS2Crew:
-            self._usingFS2Crew = usingFS2Crew
-            self._modified = True
-
-    @property
-    def iasSmoothingLength(self):
-        """Get the number of samples over which the IAS is averaged for the
-        smoothed IAS calculation. It may be negative, in which case smoothing
-        is disabled, but we nevertheless store the number of seconds in case it
-        may become useful later."""
-        return self._iasSmoothingLength
-
-    @property
-    def realIASSmoothingLength(self):
-        """Get the real smoothing length of IAS."""
-        return max(self._iasSmoothingLength, 1)
-
-    @iasSmoothingLength.setter
-    def iasSmoothingLength(self, iasSmoothingLength):
-        """Set the number of samples over which the IAS is averaged for the
-        smoothed IAS calculation."""
-        if iasSmoothingLength!=self._iasSmoothingLength:
-            self._iasSmoothingLength = iasSmoothingLength
-            self._modified = True
-
-    @property
-    def vsSmoothingLength(self):
-        """Get the number of samples over which the VS is averaged for the
-        smoothed VS calculation. It may be negative, in which case smoothing
-        is disabled, but we nevertheless store the number of seconds in case it
-        may become useful later."""
-        return self._vsSmoothingLength
-
-    @property
-    def realVSSmoothingLength(self):
-        """Get the real smoothing length of VS."""
-        return max(self._vsSmoothingLength, 1)
-
-    @vsSmoothingLength.setter
-    def vsSmoothingLength(self, vsSmoothingLength):
-        """Set the number of samples over which the VS is averaged for the
-        smoothed VS calculation."""
-        if vsSmoothingLength!=self._vsSmoothingLength:
-            self._vsSmoothingLength = vsSmoothingLength
-            self._modified = True
-
-    @property
-    def useSimBrief(self):
-        """Check if SimBrief should be used."""
-        return self._useSimBrief
-
-    @useSimBrief.setter
-    def useSimBrief(self, useSimBrief):
-        """Check if SimBrief should be used."""
-        if self._useSimBrief != useSimBrief:
-            self._useSimBrief = useSimBrief
-            self._modified = True
-
-    @property
-    def simBriefUserName(self):
-        """Get the SimBrief user name last used"""
-        return self._simBriefUserName
-
-    @simBriefUserName.setter
-    def simBriefUserName(self, simBriefUserName):
-        """Set the SimBrief user name to be used next."""
-        if self._simBriefUserName != simBriefUserName:
-            self._simBriefUserName = simBriefUserName
-            self._modified = True
-
-    @property
-    def simBriefPassword(self):
-        """Get the SimBrief password last used"""
-        return self._simBriefPassword
-
-    @simBriefPassword.setter
-    def simBriefPassword(self, simBriefPassword):
-        """Set the SimBrief password to be used next."""
-        if self._simBriefPassword != simBriefPassword:
-            self._simBriefPassword = simBriefPassword
-            self._modified = True
-
-    @property
-    def rememberSimBriefPassword(self):
-        """Get if we should remember the SimBrief password."""
-        return self._rememberSimBriefPassword
-
-    @rememberSimBriefPassword.setter
-    def rememberSimBriefPassword(self, rememberSimBriefPassword):
-        """Set if we should remember the SimBrief password."""
-        if rememberSimBriefPassword!=self._rememberSimBriefPassword:
-            self._rememberSimBriefPassword = rememberSimBriefPassword
-            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
-            if self._pirepDirectory is None:
-                self._pirepAutoSave = False
-            self._modified = True
-
-    @property
-    def pirepAutoSave(self):
-        """Get whether the PIREP should be saved automatically when it becomes
-        saveable."""
-        return self._pirepAutoSave
-
-    @pirepAutoSave.setter
-    def pirepAutoSave(self, pirepAutoSave):
-        """Set whether the PIREP should be saved automatically when it becomes
-        saveable."""
-        pirepAutoSave = pirepAutoSave and self._pirepDirectory is not None
-        if pirepAutoSave!=self._pirepAutoSave:
-            self._pirepAutoSave = pirepAutoSave
-            self._modified = True
-
-    @property
-    def defaultMSFS(self):
-        """Get if the default simulator type is MS FS."""
-        return self._defaultMSFS
-
-    @defaultMSFS.setter
-    def defaultMSFS(self, defaultMSFS):
-        """Set if the default simulator type is MS FS."""
-        if defaultMSFS!=self._defaultMSFS:
-            self._defaultMSFS = defaultMSFS
-            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 enableSounds(self):
-        """Get whether background sounds are enabled."""
-        return self._enableSounds
-
-    @enableSounds.setter
-    def enableSounds(self, enableSounds):
-        """Set whether background sounds are enabled."""
-        if enableSounds!=self._enableSounds:
-            self._enableSounds = enableSounds
-            self._modified = True
-
-    @property
-    def pilotControlsSounds(self):
-        """Get whether the pilot controls the background sounds."""
-        return self._pilotControlsSounds
-
-    @pilotControlsSounds.setter
-    def pilotControlsSounds(self, pilotControlsSounds):
-        """Set whether the pilot controls the background sounds."""
-        if pilotControlsSounds!=self._pilotControlsSounds:
-            self._pilotControlsSounds = pilotControlsSounds
-            self._modified = True
-
-    @property
-    def pilotHotkey(self):
-        """Get the pilot's hotkey."""
-        return self._pilotHotkey
-
-    @pilotHotkey.setter
-    def pilotHotkey(self, pilotHotkey):
-        """Set the pilot's hotkey."""
-        if pilotHotkey!=self._pilotHotkey:
-            self._pilotHotkey = pilotHotkey
-            self._modified = True
-
-    @property
-    def enableApproachCallouts(self):
-        """Get whether the approach callouts should be played."""
-        return self._enableApproachCallouts
-
-    @enableApproachCallouts.setter
-    def enableApproachCallouts(self, enableApproachCallouts):
-        """Set whether the approach callouts should be played."""
-        if enableApproachCallouts!=self._enableApproachCallouts:
-            self._enableApproachCallouts = enableApproachCallouts
-            self._modified = True
-
-    @property
-    def speedbrakeAtTD(self):
-        """Get whether the speedbrake sounds should be played at touchdown."""
-        return self._speedbrakeAtTD
-
-    @speedbrakeAtTD.setter
-    def speedbrakeAtTD(self, speedbrakeAtTD):
-        """Set whether the speedbrake sounds should be played at touchdown."""
-        if speedbrakeAtTD!=self._speedbrakeAtTD:
-            self._speedbrakeAtTD = speedbrakeAtTD
-            self._modified = True
-
-    @property
-    def enableChecklists(self):
-        """Get whether aircraft-specific checklists should be played."""
-        return self._enableChecklists
-
-    @enableChecklists.setter
-    def enableChecklists(self, enableChecklists):
-        """Get whether aircraft-specific checklists should be played."""
-        if enableChecklists!=self._enableChecklists:
-            self._enableChecklists = enableChecklists
-            self._modified = True
-
-    @property
-    def checklistHotkey(self):
-        """Get the checklist hotkey."""
-        return self._checklistHotkey
-
-    @checklistHotkey.setter
-    def checklistHotkey(self, checklistHotkey):
-        """Set the checklist hotkey."""
-        if checklistHotkey!=self._checklistHotkey:
-            self._checklistHotkey = checklistHotkey
-            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 getChecklist(self, aircraftType):
-        """Get the checklist for the given aircraft type."""
-        return self._checklists[aircraftType]
-
-    def setChecklist(self, aircraftType, checklist):
-        """Set the checklist for the given aircraft type."""
-        if checklist!=self._checklists[aircraftType]:
-            self._checklists[aircraftType] = checklist.clone()
-            self._modified = True
-
-    def getApproachCallouts(self, aircraftType):
-        """Get the approach callouts for the given aircraft type."""
-        return self._approachCallouts[aircraftType]
-
-    def setApproachCallouts(self, aircraftType, approachCallouts):
-        """Set the approach callouts for the given aircraft type."""
-        if not approachCallouts==self._approachCallouts[aircraftType]:
-            self._approachCallouts[aircraftType] = approachCallouts.clone()
-            self._modified = True
-
-    def load(self):
-        """Load the configuration from its default location."""
-        try:
-            config = ConfigParser.RawConfigParser()
-            config.read(configPath)
-        except:
-            traceback.print_exc()
-            return
-
-        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._quitOnClose = self._getBoolean(config, "general",
-                                             "quitOnClose", False)
-
-        self._onlineGateSystem = self._getBoolean(config, "general",
-                                                  "onlineGateSystem",
-                                                  not secondaryInstallation)
-        self._onlineACARS = self._getBoolean(config, "general",
-                                             "onlineACARS",
-                                             not secondaryInstallation)
-        self._flareTimeFromFS = self._getBoolean(config, "general",
-                                                 "flareTimeFromFS",
-                                                 False)
-        self._syncFSTime = self._getBoolean(config, "general",
-                                            "syncFSTime",
-                                            False)
-        self._usingFS2Crew = self._getBoolean(config, "general",
-                                              "usingFS2Crew",
-                                              False)
-        self._iasSmoothingLength = int(self._get(config, "general",
-                                                 "iasSmoothingLength",
-                                                 -2))
-        self._vsSmoothingLength = int(self._get(config, "general",
-                                                "vsSmoothingLength",
-                                                -2))
-
-        self._useSimBrief = self._getBoolean(config, "simbrief",
-                                             "use", False)
-        self._simBriefUserName = self._get(config, "simbrief",
-                                           "username", "")
-        self._simBriefPassword = self._get(config, "simbrief",
-                                           "password", "")
-        self._rememberSimBriefPassword = self._getBoolean(config, "simbrief",
-                                                          "rememberPassword",
-                                                          False)
-
-        self._pirepDirectory = self._get(config, "general",
-                                         "pirepDirectory", None)
-
-        self._pirepAutoSave = self._getBoolean(config, "general",
-                                               "pirepAutoSave", False)
-        if self._pirepDirectory is None:
-            self._pirepAutoSave = False
-
-        self._messageTypeLevels = {}
-        for messageType in const.messageTypes:
-            self._messageTypeLevels[messageType] = \
-                self._getMessageTypeLevel(config, messageType)
-
-        self._enableSounds = self._getBoolean(config, "sounds", "enable",
-                                              not secondaryInstallation)
-        self._pilotControlsSounds = self._getBoolean(config, "sounds",
-                                                     "pilotControls", True)
-        self._pilotHotkey.set(self._get(config, "sounds",
-                                        "pilotHotkey", "C0"))
-        self._enableApproachCallouts = \
-            self._getBoolean(config, "sounds", "enableApproachCallouts", False)
-        self._speedbrakeAtTD = self._getBoolean(config, "sounds",
-                                                "speedbrakeAtTD", True)
-
-        self._enableChecklists = self._getBoolean(config, "sounds",
-                                                  "enableChecklists", False)
-        self._checklistHotkey.set(self._get(config, "sounds",
-                                            "checklistHotkey", "CS0"))
-
-        self._autoUpdate = self._getBoolean(config, "update", "auto", True)
-        self._updateURL = self._get(config, "update", "url",
-                                    Config.DEFAULT_UPDATE_URL +
-                                    ("/exp" if secondaryInstallation else ""))
-
-        for aircraftType in const.aircraftTypes:
-            self._checklists[aircraftType] = \
-                Checklist.fromConfig(config, aircraftType)
-            self._approachCallouts[aircraftType] = \
-                ApproachCallouts.fromConfig(config, aircraftType)
-
-        self._defaultMSFS = self._getBoolean(config, "general",
-                                             "defaultMSFS", os.name=="nt")
-
-        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", "quitOnClose",
-                   "yes" if self._quitOnClose 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")
-        config.set("general", "usingFS2Crew",
-                   "yes" if self._usingFS2Crew else "no")
-        config.set("general", "iasSmoothingLength",
-                   str(self._iasSmoothingLength))
-        config.set("general", "vsSmoothingLength",
-                   str(self._vsSmoothingLength))
-
-        config.add_section("simbrief")
-        config.set("simbrief", "use",
-                   "yes" if self._useSimBrief else "no")
-        config.set("simbrief", "username", self._simBriefUserName)
-        config.set("simbrief", "password", self._simBriefPassword)
-        config.set("simbrief", "rememberPassword",
-                   "yes" if self._rememberSimBriefPassword else "no")
-
-        if self._pirepDirectory is not None:
-            config.set("general", "pirepDirectory", self._pirepDirectory)
-        config.set("general", "pirepAutoSave",
-                   "yes" if self._pirepAutoSave else "no")
-
-        config.set("general", "defaultMSFS",
-                   "yes" if self._defaultMSFS else "no")
-
-        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("sounds")
-        config.set("sounds", "enable",
-                   "yes" if self._enableSounds else "no")
-        config.set("sounds", "pilotControls",
-                   "yes" if self._pilotControlsSounds else "no")
-        config.set("sounds", "pilotHotkey", str(self._pilotHotkey))
-        config.set("sounds", "enableApproachCallouts",
-                   "yes" if self._enableApproachCallouts else "no")
-        config.set("sounds", "speedbrakeAtTD",
-                   "yes" if self._speedbrakeAtTD else "no")
-
-        config.set("sounds", "enableChecklists",
-                   "yes" if self._enableChecklists else "no")
-        config.set("sounds", "checklistHotkey",
-                   str(self._checklistHotkey))
-
-        config.add_section("update")
-        config.set("update", "auto",
-                   "yes" if self._autoUpdate else "no")
-        config.set("update", "url", self._updateURL)
-
-        config.add_section(Checklist.SECTION)
-        config.add_section(ApproachCallouts.SECTION)
-        for aircraftType in const.aircraftTypes:
-            self._checklists[aircraftType].toConfig(config, aircraftType)
-            self._approachCallouts[aircraftType].toConfig(config, aircraftType)
-
-        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
-
-            print "Configuration saved:"
-            self.log()
-
-        except Exception, e:
-            print >> sys.stderr, "Failed to update config: " + \
-                                 utf2unicode(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 secondaryInstallation:
-            return const.MESSAGELEVEL_NONE
-        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]
-
-    def log(self):
-        """Log the configuration by printing the values"""
-        print "  pilot ID:", self._pilotID
-        print "  rememberPassword:", self._rememberPassword
-
-        print "  language:", self._language
-
-        print "  hideMinimizedWindow:", self._hideMinimizedWindow
-        print "  quitOnClose:", self._quitOnClose
-
-        print "  onlineGateSystem:", self._onlineGateSystem
-        print "  onlineACARS:", self._onlineACARS
-
-        print "  flareTimeFromFS:", self._flareTimeFromFS
-        print "  syncFSTime:", self._syncFSTime
-        print "  usingFS2Crew:", self._usingFS2Crew
-
-        print "  iasSmoothingLength:", self._iasSmoothingLength
-        print "  vsSmoothingLength:", self._vsSmoothingLength
-
-        print "  useSimBrief:", self._useSimBrief
-        print "  simBriefUserName:", self._simBriefUserName
-        print "  rememberSimBriefPassword:", self._rememberSimBriefPassword
-
-        print "  pirepDirectory:", self._pirepDirectory
-        print "  pirepAutoSave:", self._pirepAutoSave
-
-        print "  defaultMSFS:", self._defaultMSFS
-
-        print "  enableSounds:", self._enableSounds
-
-        print "  pilotControlsSounds:", self._pilotControlsSounds
-        print "  pilotHotkey:", str(self._pilotHotkey)
-
-        print "  enableApproachCallouts:", self._enableApproachCallouts
-        print "  speedbrakeAtTD:", self._speedbrakeAtTD
-
-        print "  enableChecklists:", self._enableChecklists
-        print "  checklistHotkey:", str(self._checklistHotkey)
-
-        print "  autoUpdate:", self._autoUpdate
-        print "  updateURL:", self._updateURL
-
-        print "  messageTypeLevels:"
-        for (type, level) in self._messageTypeLevels.iteritems():
-            print "    %s: %s" % (const.messageType2string(type),
-                                  const.messageLevel2string(level))
-
-        print "  checklists:"
-        for (type, checklist) in self._checklists.iteritems():
-            print "    %s:" % (const.icaoCodes[type],)
-            for path in checklist:
-                print "      " + path
-
-        print "  approachCallouts:"
-        for (type, approachCallouts) in self._approachCallouts.iteritems():
-            print "    %s:" % (const.icaoCodes[type],)
-            for (altitude, path) in approachCallouts:
-                print "      %d: %s" % (altitude, path)
-
-#-------------------------------------------------------------------------------
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 682)
+++ src/mlx/const.py	(revision 682)
@@ -0,0 +1,478 @@
+
+import sys
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.const
+#
+# The constants used by the program.
+
+#-------------------------------------------------------------------------------
+
+## The version of the program
+VERSION="0.35.2cef"
+
+#-------------------------------------------------------------------------------
+
+## 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
+
+#-------------------------------------------------------------------------------
+
+## The ratio between knots and km/h
+KNOTSTOKMPH=1.852
+
+## The ratio between km/h and knots
+KMPHTOKNOTS=1/1.852
+
+#-------------------------------------------------------------------------------
+
+## 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
+
+## Flight simulator type: Prepar3D
+SIM_P3D = 5
+
+#-------------------------------------------------------------------------------
+
+## 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-800 (charter configuration)
+AIRCRAFT_B738C = 16
+
+## 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
+
+## Aircraft type: British Aerospace BAe-146
+AIRCRAFT_B462 = 17
+
+#-------------------------------------------------------------------------------
+
+## The list of aircraft types that we know of
+# The order is mostly from most recent to oldest considering
+# Malev's history
+aircraftTypes = [AIRCRAFT_B736, AIRCRAFT_B737,
+                 AIRCRAFT_B738, AIRCRAFT_B738C,
+                 AIRCRAFT_DH8D,
+                 AIRCRAFT_F70, AIRCRAFT_CRJ2,
+                 AIRCRAFT_B762, AIRCRAFT_B763,
+                 AIRCRAFT_B733, AIRCRAFT_B734, AIRCRAFT_B735,
+                 AIRCRAFT_T154, AIRCRAFT_T134,
+                 AIRCRAFT_YK40, AIRCRAFT_DC3,
+                 AIRCRAFT_B462]
+
+#-------------------------------------------------------------------------------
+
+## A mapping of aircraft types to their 'internal' ICAO codes (which are
+# the same as the real ICAO codes, except in a few cases)
+icaoCodes = { AIRCRAFT_B736  : "B736",
+              AIRCRAFT_B737  : "B737",
+              AIRCRAFT_B738  : "B738",
+              AIRCRAFT_B738C : "B738C",
+              AIRCRAFT_B733  : "B733",
+              AIRCRAFT_B734  : "B734",
+              AIRCRAFT_B735  : "B735",
+              AIRCRAFT_DH8D  : "DH8D",
+              AIRCRAFT_B762  : "B762",
+              AIRCRAFT_B763  : "B763",
+              AIRCRAFT_CRJ2  : "CRJ2",
+              AIRCRAFT_F70   : "F70",
+              AIRCRAFT_DC3   : "DC3",
+              AIRCRAFT_T134  : "T134",
+              AIRCRAFT_T154  : "T154",
+              AIRCRAFT_YK40  : "YK40",
+              AIRCRAFT_B462  : "B462" }
+
+#-------------------------------------------------------------------------------
+
+## 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
+
+#-------------------------------------------------------------------------------
+
+flightTypes = [ FLIGHTTYPE_SCHEDULED,
+                FLIGHTTYPE_OLDTIMER,
+                FLIGHTTYPE_VIP,
+                FLIGHTTYPE_CHARTER ]
+
+#-------------------------------------------------------------------------------
+
+_flightTypeStrings = { FLIGHTTYPE_SCHEDULED : "scheduled",
+                       FLIGHTTYPE_OLDTIMER : "ot",
+                       FLIGHTTYPE_VIP : "vip",
+                       FLIGHTTYPE_CHARTER : "charter" }
+
+def flightType2string(flightType):
+    """Get the string equivalent of the given flight type."""
+    return _flightTypeStrings[flightType] \
+           if flightType in _flightTypeStrings else None
+
+#-------------------------------------------------------------------------------
+
+## Message type: logger error
+# FIXME: cannot set the hotkey
+MESSAGETYPE_LOGGER_ERROR = 1
+
+## Message type: information
+MESSAGETYPE_INFORMATION = 2
+
+## Message type: in-flight information
+MESSAGETYPE_INFLIGHT = 3
+
+## Message type: fault messages
+MESSAGETYPE_FAULT = 4
+
+## Message type: NO-GO fault messages
+MESSAGETYPE_NOGO = 5
+
+## Message type: gate system messages
+MESSAGETYPE_GATE_SYSTEM = 6
+
+## Message type: environment messages
+# FIXME: flight plan closed (5 sec)
+MESSAGETYPE_ENVIRONMENT = 7
+
+## Message type: help messages
+MESSAGETYPE_HELP = 8
+
+## Message type: visibility messages
+MESSAGETYPE_VISIBILITY = 9
+
+#-------------------------------------------------------------------------------
+
+messageTypes = [ MESSAGETYPE_LOGGER_ERROR,
+                 MESSAGETYPE_INFORMATION,
+                 MESSAGETYPE_INFLIGHT,
+                 MESSAGETYPE_FAULT,
+                 MESSAGETYPE_NOGO,
+                 MESSAGETYPE_GATE_SYSTEM,
+                 MESSAGETYPE_ENVIRONMENT,
+                 MESSAGETYPE_HELP,
+                 MESSAGETYPE_VISIBILITY ]
+
+#-------------------------------------------------------------------------------
+
+_messageTypeStrings = { MESSAGETYPE_LOGGER_ERROR : "loggerError",
+                        MESSAGETYPE_INFORMATION : "information",
+                        MESSAGETYPE_INFLIGHT : "inflight",
+                        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"
+
+## Sound: notify
+SOUND_NOTIFY = "notify.wav"
+
+## Sound: NOTAM
+SOUND_NOTAM = "notam.mp3"
+
+## Sound: scream
+SOUND_SCREAM = "sikoly.mp3"
+
+## Sound: boarding
+SOUND_BOARDING = "board.mp3"
+
+## Sound: Malev theme
+SOUND_MALEV = "malev.mp3"
+
+## Sound: taxi: Boeing 737 NG
+SOUND_TAXI_BOEING737NG = "737taxi.mp3"
+
+## Sound: taxi: Boeing 767
+SOUND_TAXI_BOEING767 = "767taxi.mp3"
+
+## Sound: taxi: Fokker F70
+SOUND_TAXI_F70 = "F70taxi.mp3"
+
+## Sound: takeoff preparation request from the captain
+SOUND_CAPTAIN_TAKEOFF = "cpt_takeoff.mp3"
+
+## Sound: cruise
+SOUND_CRUISE = "TOC.mp3"
+
+## Sound: descent
+SOUND_DESCENT = "TOD.mp3"
+
+## Sound: applause
+SOUND_APPLAUSE = "taps.mp3"
+
+## Sound: speedbrake
+SOUND_SPEEDBRAKE = "speed.mp3"
+
+## Sound: taxi after landing
+SOUND_TAXIAFTERLAND = "TaxiAfterLand.mp3"
+
+
+#-------------------------------------------------------------------------------
+
+## 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
+
+#-------------------------------------------------------------------------------
+
+_fuelTankLogStrings = { 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 : "external 1",
+                        FUELTANK_EXTERNAL2 : "external 2",
+                        FUELTANK_CENTRE2 : "centre 2" }
+
+def fuelTank2logString(fuelTank):
+    """Get the log string equivalent of the given fuelTank."""
+    return _fuelTankLogStrings[fuelTank] \
+        if fuelTank in _fuelTankLogStrings else "unknown"
+
+#-------------------------------------------------------------------------------
+
+languages = ["$system", "en_GB", "hu_HU"]
+
+#-------------------------------------------------------------------------------
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 682)
+++ 	(revision )
@@ -1,478 +1,0 @@
-
-import sys
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.const
-#
-# The constants used by the program.
-
-#-------------------------------------------------------------------------------
-
-## The version of the program
-VERSION="0.35.2cef"
-
-#-------------------------------------------------------------------------------
-
-## 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
-
-#-------------------------------------------------------------------------------
-
-## The ratio between knots and km/h
-KNOTSTOKMPH=1.852
-
-## The ratio between km/h and knots
-KMPHTOKNOTS=1/1.852
-
-#-------------------------------------------------------------------------------
-
-## 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
-
-## Flight simulator type: Prepar3D
-SIM_P3D = 5
-
-#-------------------------------------------------------------------------------
-
-## 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-800 (charter configuration)
-AIRCRAFT_B738C = 16
-
-## 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
-
-## Aircraft type: British Aerospace BAe-146
-AIRCRAFT_B462 = 17
-
-#-------------------------------------------------------------------------------
-
-## The list of aircraft types that we know of
-# The order is mostly from most recent to oldest considering
-# Malev's history
-aircraftTypes = [AIRCRAFT_B736, AIRCRAFT_B737,
-                 AIRCRAFT_B738, AIRCRAFT_B738C,
-                 AIRCRAFT_DH8D,
-                 AIRCRAFT_F70, AIRCRAFT_CRJ2,
-                 AIRCRAFT_B762, AIRCRAFT_B763,
-                 AIRCRAFT_B733, AIRCRAFT_B734, AIRCRAFT_B735,
-                 AIRCRAFT_T154, AIRCRAFT_T134,
-                 AIRCRAFT_YK40, AIRCRAFT_DC3,
-                 AIRCRAFT_B462]
-
-#-------------------------------------------------------------------------------
-
-## A mapping of aircraft types to their 'internal' ICAO codes (which are
-# the same as the real ICAO codes, except in a few cases)
-icaoCodes = { AIRCRAFT_B736  : "B736",
-              AIRCRAFT_B737  : "B737",
-              AIRCRAFT_B738  : "B738",
-              AIRCRAFT_B738C : "B738C",
-              AIRCRAFT_B733  : "B733",
-              AIRCRAFT_B734  : "B734",
-              AIRCRAFT_B735  : "B735",
-              AIRCRAFT_DH8D  : "DH8D",
-              AIRCRAFT_B762  : "B762",
-              AIRCRAFT_B763  : "B763",
-              AIRCRAFT_CRJ2  : "CRJ2",
-              AIRCRAFT_F70   : "F70",
-              AIRCRAFT_DC3   : "DC3",
-              AIRCRAFT_T134  : "T134",
-              AIRCRAFT_T154  : "T154",
-              AIRCRAFT_YK40  : "YK40",
-              AIRCRAFT_B462  : "B462" }
-
-#-------------------------------------------------------------------------------
-
-## 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
-
-#-------------------------------------------------------------------------------
-
-flightTypes = [ FLIGHTTYPE_SCHEDULED,
-                FLIGHTTYPE_OLDTIMER,
-                FLIGHTTYPE_VIP,
-                FLIGHTTYPE_CHARTER ]
-
-#-------------------------------------------------------------------------------
-
-_flightTypeStrings = { FLIGHTTYPE_SCHEDULED : "scheduled",
-                       FLIGHTTYPE_OLDTIMER : "ot",
-                       FLIGHTTYPE_VIP : "vip",
-                       FLIGHTTYPE_CHARTER : "charter" }
-
-def flightType2string(flightType):
-    """Get the string equivalent of the given flight type."""
-    return _flightTypeStrings[flightType] \
-           if flightType in _flightTypeStrings else None
-
-#-------------------------------------------------------------------------------
-
-## Message type: logger error
-# FIXME: cannot set the hotkey
-MESSAGETYPE_LOGGER_ERROR = 1
-
-## Message type: information
-MESSAGETYPE_INFORMATION = 2
-
-## Message type: in-flight information
-MESSAGETYPE_INFLIGHT = 3
-
-## Message type: fault messages
-MESSAGETYPE_FAULT = 4
-
-## Message type: NO-GO fault messages
-MESSAGETYPE_NOGO = 5
-
-## Message type: gate system messages
-MESSAGETYPE_GATE_SYSTEM = 6
-
-## Message type: environment messages
-# FIXME: flight plan closed (5 sec)
-MESSAGETYPE_ENVIRONMENT = 7
-
-## Message type: help messages
-MESSAGETYPE_HELP = 8
-
-## Message type: visibility messages
-MESSAGETYPE_VISIBILITY = 9
-
-#-------------------------------------------------------------------------------
-
-messageTypes = [ MESSAGETYPE_LOGGER_ERROR,
-                 MESSAGETYPE_INFORMATION,
-                 MESSAGETYPE_INFLIGHT,
-                 MESSAGETYPE_FAULT,
-                 MESSAGETYPE_NOGO,
-                 MESSAGETYPE_GATE_SYSTEM,
-                 MESSAGETYPE_ENVIRONMENT,
-                 MESSAGETYPE_HELP,
-                 MESSAGETYPE_VISIBILITY ]
-
-#-------------------------------------------------------------------------------
-
-_messageTypeStrings = { MESSAGETYPE_LOGGER_ERROR : "loggerError",
-                        MESSAGETYPE_INFORMATION : "information",
-                        MESSAGETYPE_INFLIGHT : "inflight",
-                        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"
-
-## Sound: notify
-SOUND_NOTIFY = "notify.wav"
-
-## Sound: NOTAM
-SOUND_NOTAM = "notam.mp3"
-
-## Sound: scream
-SOUND_SCREAM = "sikoly.mp3"
-
-## Sound: boarding
-SOUND_BOARDING = "board.mp3"
-
-## Sound: Malev theme
-SOUND_MALEV = "malev.mp3"
-
-## Sound: taxi: Boeing 737 NG
-SOUND_TAXI_BOEING737NG = "737taxi.mp3"
-
-## Sound: taxi: Boeing 767
-SOUND_TAXI_BOEING767 = "767taxi.mp3"
-
-## Sound: taxi: Fokker F70
-SOUND_TAXI_F70 = "F70taxi.mp3"
-
-## Sound: takeoff preparation request from the captain
-SOUND_CAPTAIN_TAKEOFF = "cpt_takeoff.mp3"
-
-## Sound: cruise
-SOUND_CRUISE = "TOC.mp3"
-
-## Sound: descent
-SOUND_DESCENT = "TOD.mp3"
-
-## Sound: applause
-SOUND_APPLAUSE = "taps.mp3"
-
-## Sound: speedbrake
-SOUND_SPEEDBRAKE = "speed.mp3"
-
-## Sound: taxi after landing
-SOUND_TAXIAFTERLAND = "TaxiAfterLand.mp3"
-
-
-#-------------------------------------------------------------------------------
-
-## 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
-
-#-------------------------------------------------------------------------------
-
-_fuelTankLogStrings = { 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 : "external 1",
-                        FUELTANK_EXTERNAL2 : "external 2",
-                        FUELTANK_CENTRE2 : "centre 2" }
-
-def fuelTank2logString(fuelTank):
-    """Get the log string equivalent of the given fuelTank."""
-    return _fuelTankLogStrings[fuelTank] \
-        if fuelTank in _fuelTankLogStrings else "unknown"
-
-#-------------------------------------------------------------------------------
-
-languages = ["$system", "en_GB", "hu_HU"]
-
-#-------------------------------------------------------------------------------
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 635)
+++ src/mlx/flight.py	(revision 635)
@@ -0,0 +1,614 @@
+
+from soundsched import SoundScheduler, ChecklistScheduler
+from checks import SpeedChecker
+
+import const
+import util
+import time
+
+import threading
+
+#---------------------------------------------------------------------------------------
+
+## @package mlx.flight
+#
+# The global flight state.
+#
+# This module defines a single class, \ref Flight, which represents the flight
+# in progress.
+
+#---------------------------------------------------------------------------------------
+
+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."""
+
+    # The difference in minutes from the schedule which is considered a bit big
+    TIME_WARNING_DIFFERENCE = 5
+
+    # The difference in minutes from the schedule which is considered too big
+    TIME_ERROR_DIFFERENCE = 15
+
+    @staticmethod
+    def canLogCruiseAltitude(stage):
+        """Determine if the cruise altitude can be logged in the given
+        stage."""
+        return stage in [const.STAGE_CRUISE, const.STAGE_DESCENT,
+                         const.STAGE_LANDING]
+
+    @staticmethod
+    def isTimeDifferenceTooMuch(scheduledTime, realTimestamp):
+        """Determine if the given real time differs to much from the scheduled
+        time.
+
+        Returns a tuple of:
+        - a boolean indicating if the difference is enough to warrant at least
+          a warning
+        - a boolean indicating if the difference is too big, i. e. unacceptable
+          without explanation."""
+        realTime = time.gmtime(realTimestamp)
+
+        scheduledMinute = scheduledTime.hour * 60 + scheduledTime.minute
+        realMinute = realTime.tm_hour * 60 + realTime.tm_min
+
+        diff1 = scheduledMinute - realMinute
+        diff2 = -1 * diff1
+
+        if diff1 < 0: diff1 += 60*24
+        else: diff2 += 60*24
+
+        diff = min(diff1, diff2)
+
+        return (diff>Flight.TIME_WARNING_DIFFERENCE,
+                diff>Flight.TIME_ERROR_DIFFERENCE)
+
+    def __init__(self, logger, gui):
+        """Construct the flight."""
+        self._stage = None
+        self.logger = logger
+        self._gui = gui
+
+        gui.resetFlightStatus()
+
+        self._pilotHotkeyPressed = False
+        self._checklistHotkeyPressed = False
+
+        self.flareTimeFromFS = False
+
+        self.aircraftType = None
+        self.aircraft = None
+        self.simulator = None
+
+        self.blockTimeStart = None
+        self.flightTimeStart = None
+        self.climbTimeStart = None
+        self.cruiseTimeStart = None
+        self.descentTimeStart = None
+        self.flightTimeEnd = None
+        self.blockTimeEnd = None
+
+        self._rtoState = None
+        self._rtoLogEntryID = 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
+
+        self._tdRate = None
+
+        self._soundScheduler = SoundScheduler(self)
+        self._checklistScheduler = ChecklistScheduler(self)
+
+        self._maxAltitude = 0
+
+    @property
+    def config(self):
+        """Get the configuration."""
+        return self._gui.config
+
+    @property
+    def stage(self):
+        """Get the flight stage."""
+        return self._stage
+
+    @property
+    def fsType(self):
+        """Get the flight simulator type."""
+        return self._gui.fsType
+
+    @property
+    def loggedIn(self):
+        """Indicate if the user has logged in properly."""
+        return self._gui.loggedIn
+
+    @property
+    def entranceExam(self):
+        """Get whether an entrance exam is being performed."""
+        return self._gui.entranceExam
+
+    @property
+    def bookedFlight(self):
+        """Get the booked flight."""
+        return self._gui.bookedFlight
+
+    @property
+    def numCrew(self):
+        """Get the number of crew members on the flight."""
+        return self._gui.numCrew
+
+    @property
+    def numPassengers(self):
+        """Get the number of passengers on the flight."""
+        return self._gui.numPassengers
+
+    @property
+    def bagWeight(self):
+        """Get the baggage weight for the flight."""
+        return self._gui.bagWeight
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight for the flight."""
+        return self._gui.cargoWeight
+
+    @property
+    def mailWeight(self):
+        """Get the mail weight for the flight."""
+        return self._gui.mailWeight
+
+    @property
+    def zfw(self):
+        """Get the Zero-Fuel Weight of the flight."""
+        return self._gui.zfw
+
+    @property
+    def filedCruiseAltitude(self):
+        """Get the filed cruise altitude."""
+        return self._gui.filedCruiseAltitude
+
+    @property
+    def cruiseAltitude(self):
+        """Get the cruise altitude of the flight."""
+        return self._gui.cruiseAltitude
+
+    @property
+    def maxAltitude(self):
+        """Get the maximal altitude ever reached during the flight."""
+        return self._maxAltitude
+
+    @property
+    def cruiseAltitudeForDescent(self):
+        """Get the cruise altitude to check for descending.
+
+        This is the minimum of current maximal altitude and the cruise
+        altitude."""
+        return min(self._maxAltitude, self.cruiseAltitude)
+
+    @property
+    def route(self):
+        """Get the route of the flight."""
+        return self._gui.route
+
+    @property
+    def departureMETAR(self):
+        """Get the departure METAR of the flight."""
+        return self._gui.departureMETAR
+
+    @property
+    def arrivalMETAR(self):
+        """Get the arrival METAR of the flight."""
+        return self._gui.arrivalMETAR
+
+    @property
+    def departureRunway(self):
+        """Get the departure runway."""
+        return self._gui.departureRunway
+
+    @property
+    def sid(self):
+        """Get the SID followed."""
+        return self._gui.sid
+
+    @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 takeoffAntiIceOn(self):
+        """Get whether the anti-ice system was on during takeoff."""
+        return self._gui.takeoffAntiIceOn
+
+    @takeoffAntiIceOn.setter
+    def takeoffAntiIceOn(self, value):
+        """Set whether the anti-ice system was on during takeoff."""
+        self._gui.takeoffAntiIceOn = value
+
+    @property
+    def derate(self):
+        """Get the derate value of the flight."""
+        return self._gui.derate
+
+    @property
+    def star(self):
+        """Get the STAR planned."""
+        return self._gui.star
+
+    @property
+    def transition(self):
+        """Get the transition planned."""
+        return self._gui.transition
+
+    @property
+    def approachType(self):
+        """Get the approach type."""
+        return self._gui.approachType
+
+    @property
+    def arrivalRunway(self):
+        """Get the arrival runway."""
+        return self._gui.arrivalRunway
+
+    @property
+    def vref(self):
+        """Get the VRef speed of the flight."""
+        return self._gui.vref
+
+    @property
+    def landingAntiIceOn(self):
+        """Get whether the anti-ice system was on during landing."""
+        return self._gui.landingAntiIceOn
+
+    @landingAntiIceOn.setter
+    def landingAntiIceOn(self, value):
+        """Set whether the anti-ice system was on during landing."""
+        self._gui.landingAntiIceOn = value
+
+    @property
+    def tdRate(self):
+        """Get the touchdown rate if known, None otherwise."""
+        return self._tdRate
+
+    @property
+    def flightType(self):
+        """Get the type of the flight."""
+        return self._gui.flightType
+
+    @property
+    def online(self):
+        """Get whether the flight was an online flight."""
+        return self._gui.online
+
+    @property
+    def comments(self):
+        """Get the comments made by the pilot."""
+        return self._gui.comments
+
+    @property
+    def flightDefects(self):
+        """Get the flight defects reported by the pilot."""
+        return self._gui.flightDefects
+
+    @property
+    def delayCodes(self):
+        """Get the delay codes."""
+        return self._gui.delayCodes
+
+    @property
+    def speedInKnots(self):
+        """Determine if the speeds for the flight are to be expressed in
+        knots."""
+        return self.aircraft.speedInKnots if self.aircraft is not None \
+               else True
+
+    @property
+    def aglInFeet(self):
+        """Determine if the AGL altutides for the flight are to be expressed in
+        feet."""
+        return self.aircraft.aglInFeet if self.aircraft is not None \
+               else True
+
+    @property
+    def hasRTO(self):
+        """Determine if we have an RTO state."""
+        return self._rtoState is not None
+
+    @property
+    def rtoState(self):
+        """Get the RTO state."""
+        return self._rtoState
+
+    @property
+    def blockTimeStartWrong(self):
+        """Determine if the block time start is wrong compared to the scheduled
+        departure time.
+
+        Returns a tuple of:
+        - a boolean indicating if the difference warrants a warning
+        - a boolean indicating if the difference warrants not only a warning,
+          but an error as well."""
+        return self.isTimeDifferenceTooMuch(self.bookedFlight.departureTime,
+                                            self.blockTimeStart)
+
+    @property
+    def blockTimeEndWrong(self):
+        """Determine if the block time end is wrong compared to the scheduled
+        arrival time.
+
+        Returns a tuple of:
+        - a boolean indicating if the difference warrants a warning
+        - a boolean indicating if the difference warrants not only a warning,
+          but an error as well."""
+        return self.isTimeDifferenceTooMuch(self.bookedFlight.arrivalTime,
+                                            self.blockTimeEnd)
+
+    def disconnected(self):
+        """Called when the connection to the simulator has failed."""
+        if self.aircraft is not None and self.aircraft.state is not None:
+            self.logger.message(self.aircraft.state.timestamp,
+                                "The connection to the simulator has failed")
+
+    def handleState(self, oldState, currentState):
+        """Handle a new state information."""
+        self._updateFlownDistance(currentState)
+
+        self.endFuel = currentState.totalFuel
+        if self.startFuel is None:
+            self.startFuel = self.endFuel
+
+        self._soundScheduler.schedule(currentState,
+                                      self._pilotHotkeyPressed)
+        self._pilotHotkeyPressed = False
+
+        self._maxAltitude = max(currentState.altitude, self._maxAltitude)
+
+        if self._checklistHotkeyPressed:
+            self._checklistScheduler.hotkeyPressed()
+            self._checklistHotkeyPressed = False
+
+    def setStage(self, timestamp, stage):
+        """Set the flight stage.
+
+        Returns if the stage has really changed."""
+        if stage!=self._stage:
+            self._logStageDuration(timestamp, 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_CLIMB:
+                self.climbTimeStart = timestamp
+            elif stage==const.STAGE_CRUISE:
+                self.cruiseTimeStart = timestamp
+            elif stage==const.STAGE_DESCENT:
+                self.descentTimeStart = timestamp
+            elif stage==const.STAGE_TAXIAFTERLAND:
+                self.flightTimeEnd = timestamp
+            # elif stage==const.STAGE_PARKING:
+            #     self.blockTimeEnd = timestamp
+            elif stage==const.STAGE_END:
+                self.blockTimeEnd = timestamp
+                with self._endCondition:
+                    self._endCondition.notify()
+            return True
+        else:
+            return False
+
+    def handleFault(self, faultID, timestamp, what, score,
+                    updatePrevious = False, updateID = None):
+        """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."""
+        id = self.logger.fault(faultID, timestamp, what, score,
+                               updatePrevious = updatePrevious,
+                               updateID = updateID)
+        self._gui.setRating(self.logger.getRating())
+        return id
+
+    def handleNoGo(self, faultID, timestamp, what, shortReason):
+        """Handle a No-Go fault."""
+        self.logger.noGo(faultID, timestamp, what)
+        self._gui.setNoGo(shortReason)
+
+    def setRTOState(self, state):
+        """Set the state that might be used as the RTO state.
+
+        If there has been no RTO state, the GUI is notified that from now on
+        the user may select to report an RTO."""
+        hadNoRTOState = self._rtoState is None
+
+        self._rtoState = state
+        self._rtoLogEntryID = \
+            SpeedChecker.logSpeedFault(self, state,
+                                       stage = const.STAGE_PUSHANDTAXI)
+
+        if hadNoRTOState:
+            self._gui.updateRTO()
+
+    def rtoToggled(self, indicated):
+        """Called when the user has toggled the RTO indication."""
+        if self._rtoState is not None:
+            if indicated:
+                self.logger.clearFault(self._rtoLogEntryID,
+                                       "RTO at %d knots" %
+                                       (self._rtoState.groundSpeed,))
+                self._gui.setRating(self.logger.getRating())
+                if self._stage == const.STAGE_PUSHANDTAXI:
+                    self.setStage(self.aircraft.state.timestamp,
+                                  const.STAGE_RTO)
+            else:
+                SpeedChecker.logSpeedFault(self, self._rtoState,
+                                           stage = const.STAGE_PUSHANDTAXI,
+                                           updateID = self._rtoLogEntryID)
+                if self._stage == const.STAGE_RTO:
+                    self.setStage(self.aircraft.state.timestamp,
+                                  const.STAGE_PUSHANDTAXI)
+
+    def flareStarted(self, flareStart, flareStartFS):
+        """Called when the flare time has started."""
+        self._flareStart = flareStart
+        self._flareStartFS = flareStartFS
+
+    def flareFinished(self, flareEnd, flareEndFS, tdRate):
+        """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
+        """
+        self._tdRate = tdRate
+        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 pilotHotkeyPressed(self):
+        """Called when the pilot hotkey is pressed."""
+        self._pilotHotkeyPressed = True
+
+    def checklistHotkeyPressed(self):
+        """Called when the checklist hotkey is pressed."""
+        self._checklistHotkeyPressed = True
+
+    def speedFromKnots(self, knots):
+        """Convert the given speed value expressed in knots into the flight's
+        speed unit."""
+        return knots if self.speedInKnots else knots * const.KNOTSTOKMPH
+
+    def aglFromFeet(self, feet):
+        """Convert the given AGL altitude value expressed in feet into the
+        flight's AGL altitude unit."""
+        return feet if self.aglInFeet else feet * const.FEETTOMETRES
+
+    def speedToKnots(self, speed):
+        """Convert the given speed expressed in the flight's speed unit into
+        knots."""
+        return speed if self.speedInKnots else speed * const.KMPHTOKNOTS
+
+    def getEnglishSpeedUnit(self):
+        """Get the English name of the speed unit used by the flight."""
+        return "knots" if self.speedInKnots else "km/h"
+
+    def getEnglishAGLUnit(self):
+        """Get the English name of the AGL unit used by the flight."""
+        return "ft" if self.aglInFeet else "m"
+
+    def getI18NSpeedUnit(self):
+        """Get the speed unit suffix for i18n message identifiers."""
+        return "_knots" if self.speedInKnots else "_kmph"
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        fuelStr = ""
+        for (tank, amount) in aircraftState.fuel:
+            if fuelStr: fuelStr += " - "
+            fuelStr += "%s=%.0f kg" % (const.fuelTank2logString(tank), amount)
+
+        self.logger.message(aircraftState.timestamp, "Fuel: " + fuelStr)
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (aircraftState.totalFuel,))
+
+    def cruiseLevelChanged(self):
+        """Called when the cruise level hass changed."""
+        if self.canLogCruiseAltitude(self._stage):
+            message = "Cruise altitude modified to %d feet" % \
+                      (self._gui.loggableCruiseAltitude,)
+            self.logger.message(self.aircraft.timestamp, message)
+            return True
+        else:
+            return False
+
+    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]
+
+    def _logStageDuration(self, timestamp, stage):
+        """Log the duration of the stage preceding the given one."""
+        what = None
+        startTime = None
+
+        if stage==const.STAGE_TAKEOFF:
+            what = "Pushback and taxi"
+            startTime = self.blockTimeStart
+        elif stage==const.STAGE_CRUISE:
+            what = "Climb"
+            startTime = self.climbTimeStart
+        elif stage==const.STAGE_DESCENT:
+            what = "Cruise"
+            startTime = self.cruiseTimeStart
+        elif stage==const.STAGE_LANDING:
+            what = "Descent"
+            startTime = self.descentTimeStart
+        elif stage==const.STAGE_END:
+            what = "Taxi after landing"
+            startTime = self.flightTimeEnd
+
+        if what is not None and startTime is not None:
+            duration = timestamp - startTime
+            self.logger.message(timestamp,
+                                "%s time: %s" % \
+                                (what, util.getTimeIntervalString(duration)))
+
+#---------------------------------------------------------------------------------------
Index: src/mlx/flight.py
===================================================================
--- src/mlx/flight.py	(revision 635)
+++ 	(revision )
@@ -1,614 +1,0 @@
-
-from soundsched import SoundScheduler, ChecklistScheduler
-from checks import SpeedChecker
-
-import const
-import util
-import time
-
-import threading
-
-#---------------------------------------------------------------------------------------
-
-## @package mlx.flight
-#
-# The global flight state.
-#
-# This module defines a single class, \ref Flight, which represents the flight
-# in progress.
-
-#---------------------------------------------------------------------------------------
-
-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."""
-
-    # The difference in minutes from the schedule which is considered a bit big
-    TIME_WARNING_DIFFERENCE = 5
-
-    # The difference in minutes from the schedule which is considered too big
-    TIME_ERROR_DIFFERENCE = 15
-
-    @staticmethod
-    def canLogCruiseAltitude(stage):
-        """Determine if the cruise altitude can be logged in the given
-        stage."""
-        return stage in [const.STAGE_CRUISE, const.STAGE_DESCENT,
-                         const.STAGE_LANDING]
-
-    @staticmethod
-    def isTimeDifferenceTooMuch(scheduledTime, realTimestamp):
-        """Determine if the given real time differs to much from the scheduled
-        time.
-
-        Returns a tuple of:
-        - a boolean indicating if the difference is enough to warrant at least
-          a warning
-        - a boolean indicating if the difference is too big, i. e. unacceptable
-          without explanation."""
-        realTime = time.gmtime(realTimestamp)
-
-        scheduledMinute = scheduledTime.hour * 60 + scheduledTime.minute
-        realMinute = realTime.tm_hour * 60 + realTime.tm_min
-
-        diff1 = scheduledMinute - realMinute
-        diff2 = -1 * diff1
-
-        if diff1 < 0: diff1 += 60*24
-        else: diff2 += 60*24
-
-        diff = min(diff1, diff2)
-
-        return (diff>Flight.TIME_WARNING_DIFFERENCE,
-                diff>Flight.TIME_ERROR_DIFFERENCE)
-
-    def __init__(self, logger, gui):
-        """Construct the flight."""
-        self._stage = None
-        self.logger = logger
-        self._gui = gui
-
-        gui.resetFlightStatus()
-
-        self._pilotHotkeyPressed = False
-        self._checklistHotkeyPressed = False
-
-        self.flareTimeFromFS = False
-
-        self.aircraftType = None
-        self.aircraft = None
-        self.simulator = None
-
-        self.blockTimeStart = None
-        self.flightTimeStart = None
-        self.climbTimeStart = None
-        self.cruiseTimeStart = None
-        self.descentTimeStart = None
-        self.flightTimeEnd = None
-        self.blockTimeEnd = None
-
-        self._rtoState = None
-        self._rtoLogEntryID = 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
-
-        self._tdRate = None
-
-        self._soundScheduler = SoundScheduler(self)
-        self._checklistScheduler = ChecklistScheduler(self)
-
-        self._maxAltitude = 0
-
-    @property
-    def config(self):
-        """Get the configuration."""
-        return self._gui.config
-
-    @property
-    def stage(self):
-        """Get the flight stage."""
-        return self._stage
-
-    @property
-    def fsType(self):
-        """Get the flight simulator type."""
-        return self._gui.fsType
-
-    @property
-    def loggedIn(self):
-        """Indicate if the user has logged in properly."""
-        return self._gui.loggedIn
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is being performed."""
-        return self._gui.entranceExam
-
-    @property
-    def bookedFlight(self):
-        """Get the booked flight."""
-        return self._gui.bookedFlight
-
-    @property
-    def numCrew(self):
-        """Get the number of crew members on the flight."""
-        return self._gui.numCrew
-
-    @property
-    def numPassengers(self):
-        """Get the number of passengers on the flight."""
-        return self._gui.numPassengers
-
-    @property
-    def bagWeight(self):
-        """Get the baggage weight for the flight."""
-        return self._gui.bagWeight
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight for the flight."""
-        return self._gui.cargoWeight
-
-    @property
-    def mailWeight(self):
-        """Get the mail weight for the flight."""
-        return self._gui.mailWeight
-
-    @property
-    def zfw(self):
-        """Get the Zero-Fuel Weight of the flight."""
-        return self._gui.zfw
-
-    @property
-    def filedCruiseAltitude(self):
-        """Get the filed cruise altitude."""
-        return self._gui.filedCruiseAltitude
-
-    @property
-    def cruiseAltitude(self):
-        """Get the cruise altitude of the flight."""
-        return self._gui.cruiseAltitude
-
-    @property
-    def maxAltitude(self):
-        """Get the maximal altitude ever reached during the flight."""
-        return self._maxAltitude
-
-    @property
-    def cruiseAltitudeForDescent(self):
-        """Get the cruise altitude to check for descending.
-
-        This is the minimum of current maximal altitude and the cruise
-        altitude."""
-        return min(self._maxAltitude, self.cruiseAltitude)
-
-    @property
-    def route(self):
-        """Get the route of the flight."""
-        return self._gui.route
-
-    @property
-    def departureMETAR(self):
-        """Get the departure METAR of the flight."""
-        return self._gui.departureMETAR
-
-    @property
-    def arrivalMETAR(self):
-        """Get the arrival METAR of the flight."""
-        return self._gui.arrivalMETAR
-
-    @property
-    def departureRunway(self):
-        """Get the departure runway."""
-        return self._gui.departureRunway
-
-    @property
-    def sid(self):
-        """Get the SID followed."""
-        return self._gui.sid
-
-    @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 takeoffAntiIceOn(self):
-        """Get whether the anti-ice system was on during takeoff."""
-        return self._gui.takeoffAntiIceOn
-
-    @takeoffAntiIceOn.setter
-    def takeoffAntiIceOn(self, value):
-        """Set whether the anti-ice system was on during takeoff."""
-        self._gui.takeoffAntiIceOn = value
-
-    @property
-    def derate(self):
-        """Get the derate value of the flight."""
-        return self._gui.derate
-
-    @property
-    def star(self):
-        """Get the STAR planned."""
-        return self._gui.star
-
-    @property
-    def transition(self):
-        """Get the transition planned."""
-        return self._gui.transition
-
-    @property
-    def approachType(self):
-        """Get the approach type."""
-        return self._gui.approachType
-
-    @property
-    def arrivalRunway(self):
-        """Get the arrival runway."""
-        return self._gui.arrivalRunway
-
-    @property
-    def vref(self):
-        """Get the VRef speed of the flight."""
-        return self._gui.vref
-
-    @property
-    def landingAntiIceOn(self):
-        """Get whether the anti-ice system was on during landing."""
-        return self._gui.landingAntiIceOn
-
-    @landingAntiIceOn.setter
-    def landingAntiIceOn(self, value):
-        """Set whether the anti-ice system was on during landing."""
-        self._gui.landingAntiIceOn = value
-
-    @property
-    def tdRate(self):
-        """Get the touchdown rate if known, None otherwise."""
-        return self._tdRate
-
-    @property
-    def flightType(self):
-        """Get the type of the flight."""
-        return self._gui.flightType
-
-    @property
-    def online(self):
-        """Get whether the flight was an online flight."""
-        return self._gui.online
-
-    @property
-    def comments(self):
-        """Get the comments made by the pilot."""
-        return self._gui.comments
-
-    @property
-    def flightDefects(self):
-        """Get the flight defects reported by the pilot."""
-        return self._gui.flightDefects
-
-    @property
-    def delayCodes(self):
-        """Get the delay codes."""
-        return self._gui.delayCodes
-
-    @property
-    def speedInKnots(self):
-        """Determine if the speeds for the flight are to be expressed in
-        knots."""
-        return self.aircraft.speedInKnots if self.aircraft is not None \
-               else True
-
-    @property
-    def aglInFeet(self):
-        """Determine if the AGL altutides for the flight are to be expressed in
-        feet."""
-        return self.aircraft.aglInFeet if self.aircraft is not None \
-               else True
-
-    @property
-    def hasRTO(self):
-        """Determine if we have an RTO state."""
-        return self._rtoState is not None
-
-    @property
-    def rtoState(self):
-        """Get the RTO state."""
-        return self._rtoState
-
-    @property
-    def blockTimeStartWrong(self):
-        """Determine if the block time start is wrong compared to the scheduled
-        departure time.
-
-        Returns a tuple of:
-        - a boolean indicating if the difference warrants a warning
-        - a boolean indicating if the difference warrants not only a warning,
-          but an error as well."""
-        return self.isTimeDifferenceTooMuch(self.bookedFlight.departureTime,
-                                            self.blockTimeStart)
-
-    @property
-    def blockTimeEndWrong(self):
-        """Determine if the block time end is wrong compared to the scheduled
-        arrival time.
-
-        Returns a tuple of:
-        - a boolean indicating if the difference warrants a warning
-        - a boolean indicating if the difference warrants not only a warning,
-          but an error as well."""
-        return self.isTimeDifferenceTooMuch(self.bookedFlight.arrivalTime,
-                                            self.blockTimeEnd)
-
-    def disconnected(self):
-        """Called when the connection to the simulator has failed."""
-        if self.aircraft is not None and self.aircraft.state is not None:
-            self.logger.message(self.aircraft.state.timestamp,
-                                "The connection to the simulator has failed")
-
-    def handleState(self, oldState, currentState):
-        """Handle a new state information."""
-        self._updateFlownDistance(currentState)
-
-        self.endFuel = currentState.totalFuel
-        if self.startFuel is None:
-            self.startFuel = self.endFuel
-
-        self._soundScheduler.schedule(currentState,
-                                      self._pilotHotkeyPressed)
-        self._pilotHotkeyPressed = False
-
-        self._maxAltitude = max(currentState.altitude, self._maxAltitude)
-
-        if self._checklistHotkeyPressed:
-            self._checklistScheduler.hotkeyPressed()
-            self._checklistHotkeyPressed = False
-
-    def setStage(self, timestamp, stage):
-        """Set the flight stage.
-
-        Returns if the stage has really changed."""
-        if stage!=self._stage:
-            self._logStageDuration(timestamp, 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_CLIMB:
-                self.climbTimeStart = timestamp
-            elif stage==const.STAGE_CRUISE:
-                self.cruiseTimeStart = timestamp
-            elif stage==const.STAGE_DESCENT:
-                self.descentTimeStart = timestamp
-            elif stage==const.STAGE_TAXIAFTERLAND:
-                self.flightTimeEnd = timestamp
-            # elif stage==const.STAGE_PARKING:
-            #     self.blockTimeEnd = timestamp
-            elif stage==const.STAGE_END:
-                self.blockTimeEnd = timestamp
-                with self._endCondition:
-                    self._endCondition.notify()
-            return True
-        else:
-            return False
-
-    def handleFault(self, faultID, timestamp, what, score,
-                    updatePrevious = False, updateID = None):
-        """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."""
-        id = self.logger.fault(faultID, timestamp, what, score,
-                               updatePrevious = updatePrevious,
-                               updateID = updateID)
-        self._gui.setRating(self.logger.getRating())
-        return id
-
-    def handleNoGo(self, faultID, timestamp, what, shortReason):
-        """Handle a No-Go fault."""
-        self.logger.noGo(faultID, timestamp, what)
-        self._gui.setNoGo(shortReason)
-
-    def setRTOState(self, state):
-        """Set the state that might be used as the RTO state.
-
-        If there has been no RTO state, the GUI is notified that from now on
-        the user may select to report an RTO."""
-        hadNoRTOState = self._rtoState is None
-
-        self._rtoState = state
-        self._rtoLogEntryID = \
-            SpeedChecker.logSpeedFault(self, state,
-                                       stage = const.STAGE_PUSHANDTAXI)
-
-        if hadNoRTOState:
-            self._gui.updateRTO()
-
-    def rtoToggled(self, indicated):
-        """Called when the user has toggled the RTO indication."""
-        if self._rtoState is not None:
-            if indicated:
-                self.logger.clearFault(self._rtoLogEntryID,
-                                       "RTO at %d knots" %
-                                       (self._rtoState.groundSpeed,))
-                self._gui.setRating(self.logger.getRating())
-                if self._stage == const.STAGE_PUSHANDTAXI:
-                    self.setStage(self.aircraft.state.timestamp,
-                                  const.STAGE_RTO)
-            else:
-                SpeedChecker.logSpeedFault(self, self._rtoState,
-                                           stage = const.STAGE_PUSHANDTAXI,
-                                           updateID = self._rtoLogEntryID)
-                if self._stage == const.STAGE_RTO:
-                    self.setStage(self.aircraft.state.timestamp,
-                                  const.STAGE_PUSHANDTAXI)
-
-    def flareStarted(self, flareStart, flareStartFS):
-        """Called when the flare time has started."""
-        self._flareStart = flareStart
-        self._flareStartFS = flareStartFS
-
-    def flareFinished(self, flareEnd, flareEndFS, tdRate):
-        """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
-        """
-        self._tdRate = tdRate
-        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 pilotHotkeyPressed(self):
-        """Called when the pilot hotkey is pressed."""
-        self._pilotHotkeyPressed = True
-
-    def checklistHotkeyPressed(self):
-        """Called when the checklist hotkey is pressed."""
-        self._checklistHotkeyPressed = True
-
-    def speedFromKnots(self, knots):
-        """Convert the given speed value expressed in knots into the flight's
-        speed unit."""
-        return knots if self.speedInKnots else knots * const.KNOTSTOKMPH
-
-    def aglFromFeet(self, feet):
-        """Convert the given AGL altitude value expressed in feet into the
-        flight's AGL altitude unit."""
-        return feet if self.aglInFeet else feet * const.FEETTOMETRES
-
-    def speedToKnots(self, speed):
-        """Convert the given speed expressed in the flight's speed unit into
-        knots."""
-        return speed if self.speedInKnots else speed * const.KMPHTOKNOTS
-
-    def getEnglishSpeedUnit(self):
-        """Get the English name of the speed unit used by the flight."""
-        return "knots" if self.speedInKnots else "km/h"
-
-    def getEnglishAGLUnit(self):
-        """Get the English name of the AGL unit used by the flight."""
-        return "ft" if self.aglInFeet else "m"
-
-    def getI18NSpeedUnit(self):
-        """Get the speed unit suffix for i18n message identifiers."""
-        return "_knots" if self.speedInKnots else "_kmph"
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        fuelStr = ""
-        for (tank, amount) in aircraftState.fuel:
-            if fuelStr: fuelStr += " - "
-            fuelStr += "%s=%.0f kg" % (const.fuelTank2logString(tank), amount)
-
-        self.logger.message(aircraftState.timestamp, "Fuel: " + fuelStr)
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (aircraftState.totalFuel,))
-
-    def cruiseLevelChanged(self):
-        """Called when the cruise level hass changed."""
-        if self.canLogCruiseAltitude(self._stage):
-            message = "Cruise altitude modified to %d feet" % \
-                      (self._gui.loggableCruiseAltitude,)
-            self.logger.message(self.aircraft.timestamp, message)
-            return True
-        else:
-            return False
-
-    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]
-
-    def _logStageDuration(self, timestamp, stage):
-        """Log the duration of the stage preceding the given one."""
-        what = None
-        startTime = None
-
-        if stage==const.STAGE_TAKEOFF:
-            what = "Pushback and taxi"
-            startTime = self.blockTimeStart
-        elif stage==const.STAGE_CRUISE:
-            what = "Climb"
-            startTime = self.climbTimeStart
-        elif stage==const.STAGE_DESCENT:
-            what = "Cruise"
-            startTime = self.cruiseTimeStart
-        elif stage==const.STAGE_LANDING:
-            what = "Descent"
-            startTime = self.descentTimeStart
-        elif stage==const.STAGE_END:
-            what = "Taxi after landing"
-            startTime = self.flightTimeEnd
-
-        if what is not None and startTime is not None:
-            duration = timestamp - startTime
-            self.logger.message(timestamp,
-                                "%s time: %s" % \
-                                (what, util.getTimeIntervalString(duration)))
-
-#---------------------------------------------------------------------------------------
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 666)
+++ src/mlx/fs.py	(revision 666)
@@ -0,0 +1,285 @@
+
+import const
+from sound import startSound
+
+import os
+
+import fsuipc
+import xplane
+
+import threading
+import time
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.fs
+#
+# The main interface to the flight simulator.
+#
+# The \ref createSimulator function can be used to create an instance of
+# the class that can be used to access the simulator. It expects an instance of
+# the \ref ConnectionListener class, the member functions of which will be
+# called when something happens with the connection to the simulator.
+#
+# The simulator interface is most often used to retrieve the state of the
+# simulated aircraft. Instances of class \ref AircraftState are used for this
+# purpose.
+#
+# This module also contains some definitions for message sending and implements
+# the timing logic itself.
+
+#-------------------------------------------------------------------------------
+
+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
+    """
+    if type in [const.SIM_MSFS9, const.SIM_MSFSX]:
+        return fsuipc.Simulator(connectionListener, connectAttempts = 3)
+    elif type in [const.SIM_XPLANE9, const.SIM_XPLANE10]:
+        return xplane.Simulator(connectionListener, connectAttempts = 3)
+    else:
+        "Only MS Flight Simulator 2004 and X or X-Plane 9 and 10 are supported"
+
+#-------------------------------------------------------------------------------
+
+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.notify()
+        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_NOTIFY
+                       if messageType==const.MESSAGETYPE_VISIBILITY
+                       else 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 disconnect:
+            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)
+    - smoothedIAS: the smoothed IAS in knots (float)
+    - mach: the airspeed in mach (float)
+    - groundSpeed: the ground speed (float)
+    - vs: the vertical speed in feet/minutes (float)
+    - smoothedVS: the smoothed VS 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)
+    - fuel[]: the fuel information. It is a list of tuples with items:
+    the fuel tank identifier and the amount of fuel in that tank in
+    kgs
+    - totalFuel: the total amount of fuel in kg
+    - 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, for other aircraft it is None)
+    - 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, for other aircraft it is None)
+    - 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. If
+    the detection of the state of the landing lights is unreliable, and should
+    not be considered, this is set to None.
+    - 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. If
+    the detection of the state of the landing lights is unreliable, and should
+    not be considered, this is set to None.
+    - pitotHeatOn: a boolean indicating if the pitot heat is on. Maybe None, if
+      the pitot heat setting cannot be read
+    - parking: a boolean indicating if the parking brake is set
+    - gearControlDown: a boolean indicating if the gear control is set to down
+    - 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)
+    - qnh: the QNH in hPa (float)
+    - altimeterReliable: a boolean indicating if the altimeter setting is
+    reliable
+    - ils: the frequency of the ILS radio in MHz (string). Can be None, if
+    the frequency is unreliable or meaningless.
+    - ols_obs: the OBS setting of the ILS radio in degrees (int). Can be None, if
+    the value is unreliable or meaningless.
+    - ils_manual: a boolean indicating if the ILS radio is on manual control
+    - nav1: the frequency of the NAV1 radio in MHz (string). Can be None, if
+    the frequency is unreliable or meaningless.
+    - nav1_obs: the OBS setting of the NAV1 radio in degrees (int). Can be None, if
+    the value is unreliable or meaningless.
+    - nav1_manual: a boolean indicating if the NAV1 radio is on manual control
+    - nav2: the frequency of the NAV1 radio in MHz (string). Can be None, if
+    the frequency is unreliable or meaningless.
+    - nav2_obs: the OBS setting of the NAV2 radio in degrees (int). Can be None, if
+    the value is unreliable or meaningless.
+    - nav2_manual: a boolean indicating if the NAV2 radio is on manual control
+    - adf1: the frequency of the ADF1 radio in kHz (string). Can be None, if
+    the frequency is unreliable or meaningless.
+    - adf2: the frequency of the ADF2 radio in kHz (string). Can be None, if
+    the frequency is unreliable or meaningless.
+    - 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)
+    - cog: the centre of gravity
+    - xpdrC: a boolean indicating whether the transponder is in C mode, or
+      None, if the state cannot be read properly
+    - autoXPDR: a boolean indicating whether the transponder is turned on
+      automatically if the aircraft becomes airborne
+    - apMaster: a boolean indicating whether the autopilot is switched on, or
+      None, if the state cannot be read properly
+    - apHeadingHold: a boolean indicating whether the autopilot's heading hold
+      mode is switched on, or None, if the state cannot be read properly
+    - apHeading: the autopilot heading value in degrees (float),
+      or None, if the state cannot be read properly
+    - apAltitudeHold: a boolean indicating whether the autopilot's altitude hold
+      mode is switched on, or None, if the state cannot be read properly
+    - apAltitude: the autopilot altitude value in feet (float),
+      or None, if the state cannot be read properly
+    - elevatorTrim: a float value indicating the deflection of the elevator
+      trim in degrees
+    - antiIceOn: a boolean value indicating if some anti-ice system is turned
+      on. It may be None, if the state of the anti-ice system cannot be read
+      reliably
+    """
Index: src/mlx/fs.py
===================================================================
--- src/mlx/fs.py	(revision 666)
+++ 	(revision )
@@ -1,285 +1,0 @@
-
-import const
-from sound import startSound
-
-import os
-
-import fsuipc
-import xplane
-
-import threading
-import time
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.fs
-#
-# The main interface to the flight simulator.
-#
-# The \ref createSimulator function can be used to create an instance of
-# the class that can be used to access the simulator. It expects an instance of
-# the \ref ConnectionListener class, the member functions of which will be
-# called when something happens with the connection to the simulator.
-#
-# The simulator interface is most often used to retrieve the state of the
-# simulated aircraft. Instances of class \ref AircraftState are used for this
-# purpose.
-#
-# This module also contains some definitions for message sending and implements
-# the timing logic itself.
-
-#-------------------------------------------------------------------------------
-
-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
-    """
-    if type in [const.SIM_MSFS9, const.SIM_MSFSX]:
-        return fsuipc.Simulator(connectionListener, connectAttempts = 3)
-    elif type in [const.SIM_XPLANE9, const.SIM_XPLANE10]:
-        return xplane.Simulator(connectionListener, connectAttempts = 3)
-    else:
-        "Only MS Flight Simulator 2004 and X or X-Plane 9 and 10 are supported"
-
-#-------------------------------------------------------------------------------
-
-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.notify()
-        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_NOTIFY
-                       if messageType==const.MESSAGETYPE_VISIBILITY
-                       else 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 disconnect:
-            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)
-    - smoothedIAS: the smoothed IAS in knots (float)
-    - mach: the airspeed in mach (float)
-    - groundSpeed: the ground speed (float)
-    - vs: the vertical speed in feet/minutes (float)
-    - smoothedVS: the smoothed VS 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)
-    - fuel[]: the fuel information. It is a list of tuples with items:
-    the fuel tank identifier and the amount of fuel in that tank in
-    kgs
-    - totalFuel: the total amount of fuel in kg
-    - 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, for other aircraft it is None)
-    - 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, for other aircraft it is None)
-    - 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. If
-    the detection of the state of the landing lights is unreliable, and should
-    not be considered, this is set to None.
-    - 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. If
-    the detection of the state of the landing lights is unreliable, and should
-    not be considered, this is set to None.
-    - pitotHeatOn: a boolean indicating if the pitot heat is on. Maybe None, if
-      the pitot heat setting cannot be read
-    - parking: a boolean indicating if the parking brake is set
-    - gearControlDown: a boolean indicating if the gear control is set to down
-    - 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)
-    - qnh: the QNH in hPa (float)
-    - altimeterReliable: a boolean indicating if the altimeter setting is
-    reliable
-    - ils: the frequency of the ILS radio in MHz (string). Can be None, if
-    the frequency is unreliable or meaningless.
-    - ols_obs: the OBS setting of the ILS radio in degrees (int). Can be None, if
-    the value is unreliable or meaningless.
-    - ils_manual: a boolean indicating if the ILS radio is on manual control
-    - nav1: the frequency of the NAV1 radio in MHz (string). Can be None, if
-    the frequency is unreliable or meaningless.
-    - nav1_obs: the OBS setting of the NAV1 radio in degrees (int). Can be None, if
-    the value is unreliable or meaningless.
-    - nav1_manual: a boolean indicating if the NAV1 radio is on manual control
-    - nav2: the frequency of the NAV1 radio in MHz (string). Can be None, if
-    the frequency is unreliable or meaningless.
-    - nav2_obs: the OBS setting of the NAV2 radio in degrees (int). Can be None, if
-    the value is unreliable or meaningless.
-    - nav2_manual: a boolean indicating if the NAV2 radio is on manual control
-    - adf1: the frequency of the ADF1 radio in kHz (string). Can be None, if
-    the frequency is unreliable or meaningless.
-    - adf2: the frequency of the ADF2 radio in kHz (string). Can be None, if
-    the frequency is unreliable or meaningless.
-    - 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)
-    - cog: the centre of gravity
-    - xpdrC: a boolean indicating whether the transponder is in C mode, or
-      None, if the state cannot be read properly
-    - autoXPDR: a boolean indicating whether the transponder is turned on
-      automatically if the aircraft becomes airborne
-    - apMaster: a boolean indicating whether the autopilot is switched on, or
-      None, if the state cannot be read properly
-    - apHeadingHold: a boolean indicating whether the autopilot's heading hold
-      mode is switched on, or None, if the state cannot be read properly
-    - apHeading: the autopilot heading value in degrees (float),
-      or None, if the state cannot be read properly
-    - apAltitudeHold: a boolean indicating whether the autopilot's altitude hold
-      mode is switched on, or None, if the state cannot be read properly
-    - apAltitude: the autopilot altitude value in feet (float),
-      or None, if the state cannot be read properly
-    - elevatorTrim: a float value indicating the deflection of the elevator
-      trim in degrees
-    - antiIceOn: a boolean value indicating if some anti-ice system is turned
-      on. It may be None, if the state of the anti-ice system cannot be read
-      reliably
-    """
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 669)
+++ src/mlx/fsuipc.py	(revision 669)
@@ -0,0 +1,2190 @@
+
+import fs
+import const
+import util
+import acft
+from watchdog import Watchdog
+
+import threading
+import os
+import time
+import calendar
+import sys
+import codecs
+import math
+
+if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
+    import pyuipc
+else:
+    import pyuipc_sim as pyuipc
+
+#------------------------------------------------------------------------------
+
+## @package mlx.fsuipc
+#
+# The module towards FSUIPC.
+#
+# This module implements the simulator interface to FSUIPC.
+#
+# The \ref Handler class is thread handling the FSUIPC requests. It can be
+# given read, periodic read and write, requests, that are executed
+# asynchronously, and a callback function is called with the result. This class
+# is used internally within the module.
+#
+# The \ref Simulator class is the actual interface to the flight simulator, and
+# an instance of it is returned by \ref mlx.fs.createSimulator. This object can
+# be used to connect to the simulator and disconnect from it, to query various
+# data and to start and stop the monitoring of the data.
+#
+# \ref AircraftModel is the base class of the aircraft models. A "model" is a
+# particular implementation of an aircraft, such as the PMDG Boeing 737NG in
+# Flight Simulator 2004. Since each type and each model has its peculiarities
+# (e.g. the different engine and fuel tank configurations), each aircraft type
+# has a generic model, that should work for most of the time. However, certain
+# models may implement various controls, gauges, etc. differently, and such
+# peculiarites can be handled in a specific subclass of \ref
+# AircraftModel. These subclasses should be registered as special ones, and if
+# the simulator detects that the aircraft's model became known or has changed,
+# it will check these special models before resorting to the generic ones.
+#
+# The models are responsible also for querying certain parameters, such as the
+# fuel tank configuration. While ideally it should be specific to a type only,
+# it is possible that the model contains different tanks, in which case some
+# tricks may be needed. See the \ref DC3Model "DC-3 (Li-2)" aircraft as an
+# example.
+
+#------------------------------------------------------------------------------
+
+## 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, util.utf2unicode(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()
+            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._watchdogClient = Watchdog.get().addClient(2.0, "fsuipc.Handler")
+
+        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()>0:
+                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, attempts = 0):
+        """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.
+        """
+        while self._connectionRequested:
+            if attempts>=self.NUM_CONNECTATTEMPTS:
+                self._connectionRequested = False
+                if autoReconnection:
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.disconnected())
+                else:
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.connectionFailed())
+                return 0
+
+            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:
+                    fsType = const.SIM_MSFSX \
+                             if pyuipc.fs_version == pyuipc.SIM_FSX \
+                             else const.SIM_P3D \
+                             if pyuipc.fs_version == pyuipc.SIM_P3D \
+                             else const.SIM_MSFS9
+
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.connected(fsType,
+                                                                         description))
+                self._connected = True
+                return attempts
+            except Exception, e:
+                print "fsuipc.Handler._connect: connection failed: " + \
+                      util.utf2unicode(str(e)) + \
+                      " (attempts: %d)" % (attempts,)
+                if attempts<self.NUM_CONNECTATTEMPTS:
+                    time.sleep(self.CONNECT_INTERVAL)
+
+    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, attempts):
+        """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:
+            self._watchdogClient.set()
+            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 (" + \
+                      util.utf2unicode(str(e)) + \
+                      "), reconnecting (attempts=%d)." % (attempts,)
+                needReconnect = True
+
+            if needReconnect:
+                with self._requestCondition:
+                    self._requests.insert(0, request)
+                self._disconnect()
+                return self._connect(autoReconnection = True, attempts = attempts)
+            else:
+                return 0
+        finally:
+            self._watchdogClient.clear()
+            self._requestCondition.acquire()
+
+    def _processRequests(self):
+        """Process any pending requests.
+
+        Will be called with the request lock held."""
+        attempts = 0
+        while self._connectionRequested and self._periodicRequests:
+            self._periodicRequests.sort()
+            request = self._periodicRequests[0]
+
+            t = time.time()
+
+            if request.nextFire>t:
+                break
+
+            attempts = self._processRequest(request, t, attempts)
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            attempts = self._processRequest(request, None, attempts)
+
+        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
+
+    @staticmethod
+    def _appendHotkeyData(data, offset, hotkey):
+        """Append the data for the given hotkey to the given array, that is
+        intended to be passed to requestWrite call on the handler."""
+        data.append((offset + 0, "b", ord(hotkey.key)))
+
+        modifiers = 0
+        if hotkey.ctrl: modifiers |= 0x02
+        if hotkey.shift: modifiers |= 0x01
+        data.append((offset + 1, "b", modifiers))
+
+        data.append((offset + 2, "b", 0))
+
+        data.append((offset + 3, "b", 0))
+
+    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._fsType = None
+        self._aircraft = None
+
+        self._handler = Handler(self,
+                                connectAttempts = connectAttempts,
+                                connectInterval = connectInterval)
+        self._connectionListener = connectionListener
+        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._hotkeyLock = threading.Lock()
+        self._hotkeys = None
+        self._hotkeySetID = 0
+        self._hotkeySetGeneration = 0
+        self._hotkeyOffets = None
+        self._hotkeyRequestID = None
+        self._hotkeyCallback = None
+
+        self._latin1decoder = codecs.getdecoder("iso-8859-1")
+        self._fuelCallback = None
+
+    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."""
+
+        print "fsuipc.Simulator.sendMessage:", message
+
+        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, callback):
+        """Get the fuel information for the current model.
+
+        The callback will be called with a list of triplets with the following
+        items:
+        - the fuel tank identifier
+        - the current weight of the fuel in the tank (in kgs)
+        - the current total capacity of the tank (in kgs)."""
+        if self._aircraftModel is None:
+            self._fuelCallback = callback
+        else:
+            self._aircraftModel.getFuel(self._handler, 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."""
+        if self._aircraftModel is not None:
+            self._aircraftModel.setFuelLevel(self._handler, levels)
+
+    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 listenHotkeys(self, hotkeys, callback):
+        """Start listening to the given hotkeys.
+
+        callback is function expecting two arguments:
+        - the ID of the hotkey set as returned by this function,
+        - the list of the indexes of the hotkeys that were pressed."""
+        with self._hotkeyLock:
+            assert self._hotkeys is None
+
+            self._hotkeys = hotkeys
+            self._hotkeySetID += 1
+            self._hotkeySetGeneration = 0
+            self._hotkeyCallback = callback
+
+            self._handler.requestRead([(0x320c, "u")],
+                                      self._handleNumHotkeys,
+                                      (self._hotkeySetID,
+                                       self._hotkeySetGeneration))
+
+            return self._hotkeySetID
+
+    def clearHotkeys(self):
+        """Clear the current hotkey set.
+
+        Note that it is possible, that the callback function set either
+        previously or after calling this function by listenHotkeys() will be
+        called with data from the previous hotkey set.
+
+        Therefore it is recommended to store the hotkey set ID somewhere and
+        check that in the callback function. Right before calling
+        clearHotkeys(), this stored ID should be cleared so that the check
+        fails for sure."""
+        with self._hotkeyLock:
+            if self._hotkeys is not None:
+                self._hotkeys = None
+                self._hotkeySetID += 1
+                self._hotkeyCallback = None
+                self._clearHotkeyRequest()
+
+    def disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested
+
+        print "fsuipc.Simulator.disconnect", closingMessage, duration
+
+        self._stopNormal()
+        self.clearHotkeys()
+        if closingMessage is None:
+            self._handler.disconnect()
+        else:
+            self.sendMessage(closingMessage, duration = duration,
+                             _disconnect = True)
+
+    def connected(self, fsType, descriptor):
+        """Called when a connection has been established to the flight
+        simulator of the given type."""
+        self._fsType = fsType
+        with self._hotkeyLock:
+            if self._hotkeys is not None:
+                self._hotkeySetGeneration += 1
+
+                self._handler.requestRead([(0x320c, "u")],
+                                          self._handleNumHotkeys,
+                                          (self._hotkeySetID,
+                                           self._hotkeySetGeneration))
+        self._connectionListener.connected(fsType, descriptor)
+
+    def connectionFailed(self):
+        """Called when the connection could not be established."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.connectionFailed()
+
+    def disconnected(self):
+        """Called when a connection to the flight simulator has been broken."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.disconnected()
+
+    def _startDefaultNormal(self):
+        """Start the default normal periodic request."""
+        assert self._normalRequestID is None
+        self._normalRequestID = \
+             self._handler.requestPeriodicRead(1.0,
+                                               Simulator.normalData,
+                                               self._handleNormal,
+                                               validator = self._validateNormal)
+
+    def _stopNormal(self):
+        """Stop the normal period request."""
+        assert self._normalRequestID is not None
+        self._handler.clearPeriodic(self._normalRequestID)
+        self._normalRequestID = None
+        self._monitoring = False
+
+    def _validateNormal(self, data, extra):
+        """Validate the normal data."""
+        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
+
+    def _handleNormal(self, data, extra):
+        """Handle the reply to the normal request.
+
+        At the beginning the result consists the data for normalData. When
+        monitoring is started, it contains the result also for the
+        aircraft-specific values.
+        """
+        timestamp = Simulator._getTimestamp(data)
+
+        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
+        if self._fuelCallback is not None:
+            self._aircraftModel.getFuel(self._handler, self._fuelCallback)
+            self._fuelCallback = None
+
+        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."""
+        name = self._latin1decoder(name)[0]
+        airPath = self._latin1decoder(airPath)[0]
+
+        aircraftName = (name, airPath)
+        if aircraftName==self._aircraftName:
+            return False
+
+        print "fsuipc.Simulator: new aircraft name and air file path: %s, %s" % \
+              (name, airPath)
+
+        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, name, self._aircraftModel.name)
+
+        return needNew
+
+    def _setAircraftModel(self, model):
+        """Set a new aircraft model.
+
+        It will be queried for the data to monitor and the monitoring request
+        will be replaced by a new one."""
+        self._aircraftModel = model
+
+        if self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+
+    def _startMonitoring(self):
+        """Start monitoring with the current aircraft model."""
+        data = Simulator.normalData[:]
+        self._aircraftModel.addMonitoringData(data, self._fsType)
+
+        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 _handleNumHotkeys(self, data, (id, generation)):
+        """Handle the result of the query of the number of hotkeys"""
+        with self._hotkeyLock:
+            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
+                numHotkeys = data[0]
+                print "fsuipc.Simulator._handleNumHotkeys: numHotkeys:", numHotkeys
+                data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]
+                self._handler.requestRead(data, self._handleHotkeyTable,
+                                          (id, generation))
+
+    def _setupHotkeys(self, data):
+        """Setup the hiven hotkeys and return the data to be written.
+
+        If there were hotkeys set previously, they are reused as much as
+        possible. Any of them not reused will be cleared."""
+        hotkeys = self._hotkeys
+        numHotkeys = len(hotkeys)
+
+        oldHotkeyOffsets = set([] if self._hotkeyOffets is None else
+                               self._hotkeyOffets)
+
+        self._hotkeyOffets = []
+        numOffsets = 0
+
+        while oldHotkeyOffsets:
+            offset = oldHotkeyOffsets.pop()
+            self._hotkeyOffets.append(offset)
+            numOffsets += 1
+
+            if numOffsets>=numHotkeys:
+                break
+
+        for i in range(0, len(data)):
+            if numOffsets>=numHotkeys:
+                break
+
+            if data[i]==0:
+                self._hotkeyOffets.append(0x3210 + i*4)
+                numOffsets += 1
+
+        writeData = []
+        for i in range(0, numOffsets):
+            Simulator._appendHotkeyData(writeData,
+                                        self._hotkeyOffets[i],
+                                        hotkeys[i])
+
+        for offset in oldHotkeyOffsets:
+            writeData.append((offset, "u", long(0)))
+
+        return writeData
+
+    def _handleHotkeyTable(self, data, (id, generation)):
+        """Handle the result of the query of the hotkey table."""
+        with self._hotkeyLock:
+            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
+                writeData = self._setupHotkeys(data)
+                self._handler.requestWrite(writeData,
+                                           self._handleHotkeysWritten,
+                                           (id, generation))
+
+    def _handleHotkeysWritten(self, success, (id, generation)):
+        """Handle the result of the hotkeys having been written."""
+        with self._hotkeyLock:
+            if success and id==self._hotkeySetID and \
+            generation==self._hotkeySetGeneration:
+                data = [(offset + 3, "b") for offset in self._hotkeyOffets]
+
+                self._hotkeyRequestID = \
+                    self._handler.requestPeriodicRead(0.5, data,
+                                                      self._handleHotkeys,
+                                                      (id, generation))
+
+    def _handleHotkeys(self, data, (id, generation)):
+        """Handle the hotkeys."""
+        with self._hotkeyLock:
+            if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
+                return
+
+            callback = self._hotkeyCallback
+            offsets = self._hotkeyOffets
+
+        hotkeysPressed = []
+        for i in range(0, len(data)):
+            if data[i]!=0:
+                hotkeysPressed.append(i)
+
+        if hotkeysPressed:
+            data = []
+            for index in hotkeysPressed:
+                data.append((offsets[index]+3, "b", int(0)))
+            self._handler.requestWrite(data, self._handleHotkeysCleared)
+
+            callback(id, hotkeysPressed)
+
+    def _handleHotkeysCleared(self, sucess, extra):
+        """Callback for the hotkey-clearing write request."""
+
+    def _clearHotkeyRequest(self):
+        """Clear the hotkey request in the handler if there is any."""
+        if self._hotkeyRequestID is not None:
+            self._handler.clearPeriodic(self._hotkeyRequestID)
+            self._hotkeyRequestID = None
+
+#------------------------------------------------------------------------------
+
+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"),
+                      ("flapsAxis", 0x3414, "H"),
+                      ("flapsIncrement", 0x3bfa, "H"),
+                      ("lights", 0x0d0c, "H"),
+                      ("pitot", 0x029c, "b"),
+                      ("parking", 0x0bc8, "H"),
+                      ("gearControl", 0x0be8, "d"),
+                      ("noseGear", 0x0bec, "d"),
+                      ("spoilersArmed", 0x0bcc, "d"),
+                      ("spoilers", 0x0bd0, "d"),
+                      ("altimeter", 0x0330, "H"),
+                      ("qnh", 0x0ec6, "H"),
+                      ("nav1", 0x0350, "H"),
+                      ("nav1_obs", 0x0c4e, "H"),
+                      ("nav2", 0x0352, "H"),
+                      ("nav2_obs", 0x0c5e, "H"),
+                      ("adf1_main", 0x034c, "H"),
+                      ("adf1_ext", 0x0356, "H"),
+                      ("adf2_main", 0x02d4, "H"),
+                      ("adf2_ext", 0x02d6, "H"),
+                      ("squawk", 0x0354, "H"),
+                      ("windSpeed", 0x0e90, "H"),
+                      ("windDirection", 0x0e92, "H"),
+                      ("visibility", 0x0e8a, "H"),
+                      ("cog", 0x2ef8, "f"),
+                      ("xpdrC", 0x7b91, "b"),
+                      ("apMaster", 0x07bc, "d"),
+                      ("apHeadingHold", 0x07c8, "d"),
+                      ("apHeading", 0x07cc, "H"),
+                      ("apAltitudeHold", 0x07d0, "d"),
+                      ("apAltitude", 0x07d4, "u"),
+                      ("elevatorTrim", 0x2ea0, "f"),
+                      ("eng1DeIce", 0x08b2, "H"),
+                      ("eng2DeIce", 0x094a, "H"),
+                      ("propDeIce", 0x337c, "b"),
+                      ("structDeIce", 0x337d, "b")]
+
+    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]
+
+    @staticmethod
+    def convertADFFrequency(main, ext):
+        """Convert the given ADF frequency data to a string."""
+        mainBCD = AircraftModel.convertBCD(main, 4)
+        extBCD = AircraftModel.convertBCD(ext, 4)
+
+        return (extBCD[1] if extBCD[1]!="0" else "") + \
+               mainBCD[1:] + "." + extBCD[3]
+
+    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
+        self._xpdrReliable = False
+        self._flapsSet = -1
+
+    @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, fsType):
+        """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]
+        if state.flapsSet != self._flapsSet:
+            print "flapsControl: %d, flapsLeft: %d, flapsRight: %d, flapsAxis: %d, flapsIncrement: %d, flapsSet: %d, numNotchesM1: %d" % \
+                  (flapsControl, data[self._monidx_flapsLeft],
+                   data[self._monidx_flapsRight], data[self._monidx_flapsAxis],
+                   data[self._monidx_flapsIncrement], state.flapsSet, numNotchesM1)
+            self._flapsSet = state.flapsSet
+
+        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.gearControlDown = data[self._monidx_gearControl]==16383
+        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.altimeterReliable = True
+        state.qnh = data[self._monidx_qnh] / 16.0
+
+        state.ils = None
+        state.ils_obs = None
+        state.ils_manual = False
+        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
+        state.nav1_obs = data[self._monidx_nav1_obs]
+        state.nav1_manual = True
+        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
+        state.nav2_obs = data[self._monidx_nav2_obs]
+        state.nav2_manual = True
+        state.adf1 = \
+            AircraftModel.convertADFFrequency(data[self._monidx_adf1_main],
+                                              data[self._monidx_adf1_ext])
+        state.adf2 = \
+            AircraftModel.convertADFFrequency(data[self._monidx_adf2_main],
+                                              data[self._monidx_adf2_ext])
+
+        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
+
+        state.cog = data[self._monidx_cog]
+
+        if not self._xpdrReliable:
+            self._xpdrReliable = data[self._monidx_xpdrC]!=0
+
+        state.xpdrC = data[self._monidx_xpdrC]!=1 \
+                      if self._xpdrReliable else None
+        state.autoXPDR = False
+
+        state.apMaster = data[self._monidx_apMaster]!=0
+        state.apHeadingHold = data[self._monidx_apHeadingHold]!=0
+        state.apHeading = data[self._monidx_apHeading] * 360.0 / 65536.0
+        state.apAltitudeHold = data[self._monidx_apAltitudeHold]!=0
+        state.apAltitude = data[self._monidx_apAltitude] / \
+                           const.FEETTOMETRES / 65536.0
+
+
+        state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
+
+        state.antiIceOn = data[self._monidx_eng1DeIce]!=0 or \
+                          data[self._monidx_eng2DeIce]!=0 or \
+                          data[self._monidx_propDeIce]!=0 or \
+                          data[self._monidx_structDeIce]!=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, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        super(GenericAircraftModel, self).addMonitoringData(data, fsType)
+
+        self._fuelStartIndex = self._addFuelOffsets(data, "_monidx_fuelWeight")
+
+        self._engineStartIndex = len(data)
+        for i in range(0, self._numEngines):
+            self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
+            if self._isN1:
+                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
+            else:
+                self._addOffsetWithIndexMember(data, 0x0898 + i * 0x98, "H")  # RPM
+                self._addOffsetWithIndexMember(data, 0x08c8 + i * 0x98, "H")  # RPM scaler
+
+    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)
+
+        (state.fuel, state.totalFuel) = \
+            self._convertFuelData(data, index = self._monidx_fuelWeight)
+
+        state.n1 = [] if self._isN1 else None
+        state.rpm = None if self._isN1 else []
+        itemsPerEngine = 2 if self._isN1 else 3
+
+        state.reverser = []
+        for i in range(self._engineStartIndex,
+                       self._engineStartIndex +
+                       itemsPerEngine*self._numEngines,
+                       itemsPerEngine):
+            state.reverser.append(data[i]<0)
+            if self._isN1:
+                state.n1.append(data[i+1])
+            else:
+                state.rpm.append(data[i+1] * data[i+2]/65536.0)
+
+        return state
+
+    def getFuel(self, handler, callback):
+        """Get the fuel information for this model.
+
+        See Simulator.getFuel for more information. This
+        implementation simply queries the fuel tanks given to the
+        constructor."""
+        data = []
+        self._addFuelOffsets(data)
+
+        handler.requestRead(data, self._handleFuelRetrieved,
+                            extra = callback)
+
+    def setFuelLevel(self, handler, levels):
+        """Set the fuel level.
+
+        See the description of Simulator.setFuelLevel. This
+        implementation simply sets the fuel tanks as given."""
+        data = []
+        for (tank, level) in levels:
+            offset = _tank2offset[tank]
+            value = long(level * 128.0 * 65536.0)
+            data.append( (offset, "u", value) )
+
+        handler.requestWrite(data, self._handleFuelWritten)
+
+    def _addFuelOffsets(self, data, weightIndexName = None):
+        """Add the fuel offsets to the given data array.
+
+        If weightIndexName is not None, it will be the name of the
+        fuel weight index.
+
+        Returns the index of the first fuel tank's data."""
+        self._addOffsetWithIndexMember(data, 0x0af4, "H", weightIndexName)
+
+        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
+
+        return fuelStartIndex
+
+    def _convertFuelData(self, data, index = 0, addCapacities = False):
+        """Convert the given data into a fuel info list.
+
+        The list consists of two or three-tuples of the following
+        items:
+        - the fuel tank ID,
+        - the amount of the fuel in kg,
+        - if addCapacities is True, the total capacity of the tank."""
+        fuelWeight = data[index] / 256.0
+        index += 1
+
+        result = []
+        totalFuel = 0
+        for fuelTank in self._fuelTanks:
+            capacity = data[index+1] * fuelWeight * const.LBSTOKG
+            if capacity>=1.0:
+                amount = data[index] * capacity / 128.0 / 65536.0
+
+                result.append( (fuelTank, amount, capacity) if addCapacities
+                               else (fuelTank, amount))
+                totalFuel += amount
+            index += 2
+
+        return (result, totalFuel)
+
+    def _handleFuelRetrieved(self, data, callback):
+        """Callback for a fuel retrieval request."""
+        (fuelData, _totalFuel) = self._convertFuelData(data,
+                                                       addCapacities = True)
+        callback(fuelData)
+
+    def _handleFuelWritten(self, success, extra):
+        """Callback for a fuel setting request."""
+        pass
+
+#------------------------------------------------------------------------------
+
+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."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B737Model, self). \
+            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
+                     fuelTanks = B737Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Boeing 737"
+
+    # Note: the function below should be enabled if testing the speed-based
+    # takeoff on Linux
+    # def getAircraftState(self, aircraft, timestamp, data):
+    #     """Get the aircraft state.
+
+    #     Get it from the parent, and then check some PMDG-specific stuff."""
+    #     state = super(B737Model, self).getAircraftState(aircraft,
+    #                                                     timestamp,
+    #                                                     data)
+    #     state.strobeLightsOn = None
+    #     state.xpdrC = None
+
+    #     return state
+
+#------------------------------------------------------------------------------
+
+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,
+                                 const.AIRCRAFT_B738C] 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(X)"
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        self._fsType = fsType
+
+        super(PMDGBoeing737NGModel, self).addMonitoringData(data, fsType)
+
+        if fsType==const.SIM_MSFSX or fsType==const.SIM_P3D:
+            print "%s detected, adding PMDG 737 NGX-specific offsets" % \
+                  ("FSX" if fsType==const.SIM_MSFSX else "P3D",)
+            self._addOffsetWithIndexMember(data, 0x6500, "b",
+                                           "_pmdgidx_lts_positionsw")
+            self._addOffsetWithIndexMember(data, 0x6545, "b", "_pmdgidx_cmda")
+            self._addOffsetWithIndexMember(data, 0x653f, "b", "_pmdgidx_aphdgsel")
+            self._addOffsetWithIndexMember(data, 0x6543, "b", "_pmdgidx_apalthold")
+            self._addOffsetWithIndexMember(data, 0x652c, "H", "_pmdgidx_aphdg")
+            self._addOffsetWithIndexMember(data, 0x652e, "H", "_pmdgidx_apalt")
+            self._addOffsetWithIndexMember(data, 0x65cd, "b", "_pmdgidx_xpdr")
+        else:
+            print "FS9 detected, adding PMDG 737 NG-specific offsets"
+            self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
+            self._addOffsetWithIndexMember(data, 0x6216, "b", "_pmdgidx_xpdr")
+            self._addOffsetWithIndexMember(data, 0x6227, "b", "_pmdgidx_ap")
+            self._addOffsetWithIndexMember(data, 0x6228, "b", "_pmdgidx_aphdgsel")
+            self._addOffsetWithIndexMember(data, 0x622a, "b", "_pmdgidx_apalthold")
+            self._addOffsetWithIndexMember(data, 0x622c, "H", "_pmdgidx_aphdg")
+            self._addOffsetWithIndexMember(data, 0x622e, "H", "_pmdgidx_apalt")
+
+    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 self._fsType==const.SIM_MSFS9:
+            if data[self._pmdgidx_switches]&0x01==0x01:
+                state.altimeter = 1013.25
+            state.apMaster = data[self._pmdgidx_ap]&0x02==0x02
+            state.apHeadingHold = data[self._pmdgidx_aphdgsel]==2
+            apalthold = data[self._pmdgidx_apalthold]
+            state.apAltitudeHold = apalthold>=3 and apalthold<=6
+            state.xpdrC = data[self._pmdgidx_xpdr]==4
+
+            # Uncomment the following to test the speed-based takeoff
+            # state.strobeLightsOn = None
+            # state.xpdrC = None
+        else:
+            state.apMaster = data[self._pmdgidx_cmda]!=0
+            state.apHeadingHold = data[self._pmdgidx_aphdgsel]!=0
+            state.apAltitudeHold = data[self._pmdgidx_apalthold]!=0
+
+            # state.strobeLightsOn = data[self._pmdgidx_lts_positionsw]==0x02
+            # state.xpdrC = data[self._pmdgidx_xpdr]==4
+            state.strobeLightsOn = None
+            state.xpdrC = None
+
+        state.apHeading = data[self._pmdgidx_aphdg]
+        state.apAltitude = data[self._pmdgidx_apalt]
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class B767Model(GenericAircraftModel):
+    """Generic model for the Boeing 767 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B767Model, self). \
+            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
+                     fuelTanks = B767Model.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."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DH8DModel, self). \
+            __init__(flapsNotches = [0, 5, 10, 15, 35],
+                     fuelTanks = DH8DModel.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 addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        super(DreamwingsDH8DModel, self).addMonitoringData(data, fsType)
+
+        self._addOffsetWithIndexMember(data, 0x132c, "d", "_dwdh8d_navgps")
+
+    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)
+        if data[self._dwdh8d_navgps]==1:
+            state.apHeading = None
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class MajesticDH8DModel(DH8DModel):
+    """Model handler for the Majestic 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("MJC8Q400")!=-1 or \
+             airPath.lower().find("mjc8q400") or \
+             airPath.lower().find("mjc8q4.air"))
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Majestic Bombardier Dash 8-Q400"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then clear the anti-collision and landing
+        lights."""
+        state = super(MajesticDH8DModel, self).getAircraftState(aircraft,
+                                                                timestamp,
+                                                                data)
+        state.antiCollisionLightsOn = None
+        state.strobeLightsOn = None
+        state.pitotHeatOn = None
+
+        # G-load seems to be offset by -1.0 (i.e a value of 0 seem to mean
+        # a G-load of 1.0)
+        state.gLoad += 1.0
+
+        # None of the gear values seem to work correctly
+        state.gearsDown = state.gearControlDown
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class CRJ2Model(GenericAircraftModel):
+    """Generic model for the Bombardier CRJ-200 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(CRJ2Model, self). \
+            __init__(flapsNotches = [0, 8, 20, 30, 45],
+                     fuelTanks = CRJ2Model.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."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(F70Model, self). \
+            __init__(flapsNotches = [0, 8, 15, 25, 42],
+                     fuelTanks = F70Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Fokker 70"
+
+#------------------------------------------------------------------------------
+
+class DAF70Model(F70Model):
+    """Model for the Digital Aviation F70 implementation on FS9."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type == const.AIRCRAFT_F70 and \
+               (airPath.endswith("fokker70_2k4_v4.1.air") or
+                airPath.endswith("fokker70_2k4_v4.3.air"))
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Digital Aviation Fokker 70"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then invert the pitot heat state."""
+        state = super(DAF70Model, self).getAircraftState(aircraft,
+                                                         timestamp,
+                                                         data)
+        state.navLightsOn = None
+        state.landingLightsOn = None
+
+        state.altimeterReliable = False
+
+        state.ils = state.nav1
+        state.ils_obs = state.nav1_obs
+        state.ils_manual = state.nav1_manual
+
+        state.nav1 = state.nav2
+        state.nav1_obs = state.nav2_obs
+        state.nav1_manual = aircraft.flight.stage!=const.STAGE_CRUISE
+
+        state.nav2 = None
+        state.nav2_obs = None
+        state.nav2_manual = False
+
+        state.autoXPDR = True
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class DC3Model(GenericAircraftModel):
+    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT]
+    # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+    #              const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DC3Model, self). \
+            __init__(flapsNotches = [0, 15, 30, 45],
+                     fuelTanks = DC3Model.fuelTanks,
+                     numEngines = 2, isN1 = False)
+        self._leftLevel = 0.0
+        self._rightLevel = 0.0
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Lisunov Li-2 (DC-3)"
+
+    def _convertFuelData(self, data, index = 0, addCapacities = False):
+        """Convert the given data into a fuel info list.
+
+        It assumes to receive the 3 fuel tanks as seen above (left,
+        centre and right) and converts it to left aux, left, right,
+        and right aux. The amount in the left tank goes into left aux,
+        the amount of the right tank goes into right aux and the
+        amount of the centre tank goes into the left and right tanks
+        evenly distributed."""
+        (rawFuelData, totalFuel) = \
+            super(DC3Model, self)._convertFuelData(data, index, addCapacities)
+
+        centreAmount = rawFuelData[1][1]
+        if addCapacities:
+            centreCapacity = rawFuelData[1][2]
+            self._leftLevel = self._rightLevel = \
+                centreAmount / centreCapacity / 2.0
+            fuelData = [(const.FUELTANK_LEFT_AUX,
+                         rawFuelData[0][1], rawFuelData[0][2]),
+                        (const.FUELTANK_LEFT,
+                         centreAmount/2.0, centreCapacity/2.0),
+                        (const.FUELTANK_RIGHT,
+                         centreAmount/2.0, centreCapacity/2.0),
+                        (const.FUELTANK_RIGHT_AUX,
+                         rawFuelData[2][1], rawFuelData[2][2])]
+        else:
+            fuelData = [(const.FUELTANK_LEFT_AUX, rawFuelData[0][1]),
+                        (const.FUELTANK_LEFT, centreAmount/2.0),
+                        (const.FUELTANK_RIGHT, centreAmount/2.0),
+                        (const.FUELTANK_RIGHT_AUX, rawFuelData[2][1])]
+
+        return (fuelData, totalFuel)
+
+    def setFuelLevel(self, handler, levels):
+        """Set the fuel level.
+
+        See the description of Simulator.setFuelLevel. This
+        implementation assumes to get the four-tank representation,
+        as returned by getFuel()."""
+        leftLevel = None
+        centreLevel = None
+        rightLevel = None
+
+        for (tank, level) in levels:
+            if tank==const.FUELTANK_LEFT_AUX:
+                leftLevel = level if leftLevel is None else (leftLevel + level)
+            elif tank==const.FUELTANK_LEFT:
+                level /= 2.0
+                centreLevel = (self._rightLevel + level) \
+                              if centreLevel is None else (centreLevel + level)
+                self._leftLevel = level
+            elif tank==const.FUELTANK_RIGHT:
+                level /= 2.0
+                centreLevel = (self._leftLevel + level) \
+                              if centreLevel is None else (centreLevel + level)
+                self._rightLevel = level
+            elif tank==const.FUELTANK_RIGHT_AUX:
+                rightLevel = level if rightLevel is None \
+                             else (rightLevel + level)
+
+        levels = []
+        if leftLevel is not None: levels.append((const.FUELTANK_LEFT,
+                                                 leftLevel))
+        if centreLevel is not None: levels.append((const.FUELTANK_CENTRE,
+                                                   centreLevel))
+        if rightLevel is not None: levels.append((const.FUELTANK_RIGHT,
+                                                 rightLevel))
+
+        super(DC3Model, self).setFuelLevel(handler, levels)
+
+#------------------------------------------------------------------------------
+
+class T134Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
+                 const.FUELTANK_LEFT_AUX,
+                 const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT_AUX,
+                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T134Model, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelTanks = T134Model.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."""
+    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
+                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T154Model, self). \
+            __init__(flapsNotches = [0, 15, 28, 45],
+                     fuelTanks = T154Model.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 PTT154Model(T154Model):
+    """Project Tupolev Tu-154."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        print "PTT154Model.doesHandle", aircraft.type, name, airPath
+        return aircraft.type==const.AIRCRAFT_T154 and \
+               (name.find("Tu-154")!=-1 or name.find("Tu154B")!=-1) and \
+               os.path.basename(airPath).startswith("154b_")
+
+    def __init__(self):
+        """Construct the model."""
+        super(PTT154Model, self).__init__()
+        self._fsType = None
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Project Tupolev Tu-154"
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array.
+
+        It only stores the flight simulator type."""
+        self._fsType = fsType
+
+        super(PTT154Model, self).addMonitoringData(data, fsType)
+
+    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(PTT154Model, self).getAircraftState(aircraft, timestamp, data)
+
+        if self._fsType==const.SIM_MSFSX or self._fsType==const.SIM_P3D:
+            state.xpdrC = None
+
+        return state
+
+
+#------------------------------------------------------------------------------
+
+class YK40Model(GenericAircraftModel):
+    """Generic model for the Yakovlev Yak-40 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(YK40Model, self). \
+            __init__(flapsNotches = [0, 20, 35],
+                     fuelTanks = YK40Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Yakovlev Yak-40"
+
+#------------------------------------------------------------------------------
+
+class B462Model(GenericAircraftModel):
+    """Generic model for the British Aerospace BAe 146-200 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B462Model, self). \
+            __init__(flapsNotches = [0, 18, 24, 30, 33],
+                     fuelTanks = B462Model.fuelTanks,
+                     numEngines = 4)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic British Aerospace 146"
+
+    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(B462Model, self).getAircraftState(aircraft, timestamp, data)
+        state.reverser = []
+        return state
+
+#------------------------------------------------------------------------------
+
+_genericModels = { const.AIRCRAFT_B736  : B737Model,
+                   const.AIRCRAFT_B737  : B737Model,
+                   const.AIRCRAFT_B738  : B737Model,
+                   const.AIRCRAFT_B738C : 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  : CRJ2Model,
+                   const.AIRCRAFT_F70   : F70Model,
+                   const.AIRCRAFT_DC3   : DC3Model,
+                   const.AIRCRAFT_T134  : T134Model,
+                   const.AIRCRAFT_T154  : T154Model,
+                   const.AIRCRAFT_YK40  : YK40Model,
+                   const.AIRCRAFT_B462  : B462Model }
+
+#------------------------------------------------------------------------------
+
+AircraftModel.registerSpecial(PMDGBoeing737NGModel)
+AircraftModel.registerSpecial(DreamwingsDH8DModel)
+AircraftModel.registerSpecial(MajesticDH8DModel)
+AircraftModel.registerSpecial(DAF70Model)
+AircraftModel.registerSpecial(PTT154Model)
+
+#------------------------------------------------------------------------------
Index: src/mlx/fsuipc.py
===================================================================
--- src/mlx/fsuipc.py	(revision 669)
+++ 	(revision )
@@ -1,2190 +1,0 @@
-
-import fs
-import const
-import util
-import acft
-from watchdog import Watchdog
-
-import threading
-import os
-import time
-import calendar
-import sys
-import codecs
-import math
-
-if os.name == "nt" and "FORCE_PYUIPC_SIM" not in os.environ:
-    import pyuipc
-else:
-    import pyuipc_sim as pyuipc
-
-#------------------------------------------------------------------------------
-
-## @package mlx.fsuipc
-#
-# The module towards FSUIPC.
-#
-# This module implements the simulator interface to FSUIPC.
-#
-# The \ref Handler class is thread handling the FSUIPC requests. It can be
-# given read, periodic read and write, requests, that are executed
-# asynchronously, and a callback function is called with the result. This class
-# is used internally within the module.
-#
-# The \ref Simulator class is the actual interface to the flight simulator, and
-# an instance of it is returned by \ref mlx.fs.createSimulator. This object can
-# be used to connect to the simulator and disconnect from it, to query various
-# data and to start and stop the monitoring of the data.
-#
-# \ref AircraftModel is the base class of the aircraft models. A "model" is a
-# particular implementation of an aircraft, such as the PMDG Boeing 737NG in
-# Flight Simulator 2004. Since each type and each model has its peculiarities
-# (e.g. the different engine and fuel tank configurations), each aircraft type
-# has a generic model, that should work for most of the time. However, certain
-# models may implement various controls, gauges, etc. differently, and such
-# peculiarites can be handled in a specific subclass of \ref
-# AircraftModel. These subclasses should be registered as special ones, and if
-# the simulator detects that the aircraft's model became known or has changed,
-# it will check these special models before resorting to the generic ones.
-#
-# The models are responsible also for querying certain parameters, such as the
-# fuel tank configuration. While ideally it should be specific to a type only,
-# it is possible that the model contains different tanks, in which case some
-# tricks may be needed. See the \ref DC3Model "DC-3 (Li-2)" aircraft as an
-# example.
-
-#------------------------------------------------------------------------------
-
-## 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, util.utf2unicode(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()
-            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._watchdogClient = Watchdog.get().addClient(2.0, "fsuipc.Handler")
-
-        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()>0:
-                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, attempts = 0):
-        """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.
-        """
-        while self._connectionRequested:
-            if attempts>=self.NUM_CONNECTATTEMPTS:
-                self._connectionRequested = False
-                if autoReconnection:
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.disconnected())
-                else:
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.connectionFailed())
-                return 0
-
-            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:
-                    fsType = const.SIM_MSFSX \
-                             if pyuipc.fs_version == pyuipc.SIM_FSX \
-                             else const.SIM_P3D \
-                             if pyuipc.fs_version == pyuipc.SIM_P3D \
-                             else const.SIM_MSFS9
-
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.connected(fsType,
-                                                                         description))
-                self._connected = True
-                return attempts
-            except Exception, e:
-                print "fsuipc.Handler._connect: connection failed: " + \
-                      util.utf2unicode(str(e)) + \
-                      " (attempts: %d)" % (attempts,)
-                if attempts<self.NUM_CONNECTATTEMPTS:
-                    time.sleep(self.CONNECT_INTERVAL)
-
-    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, attempts):
-        """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:
-            self._watchdogClient.set()
-            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 (" + \
-                      util.utf2unicode(str(e)) + \
-                      "), reconnecting (attempts=%d)." % (attempts,)
-                needReconnect = True
-
-            if needReconnect:
-                with self._requestCondition:
-                    self._requests.insert(0, request)
-                self._disconnect()
-                return self._connect(autoReconnection = True, attempts = attempts)
-            else:
-                return 0
-        finally:
-            self._watchdogClient.clear()
-            self._requestCondition.acquire()
-
-    def _processRequests(self):
-        """Process any pending requests.
-
-        Will be called with the request lock held."""
-        attempts = 0
-        while self._connectionRequested and self._periodicRequests:
-            self._periodicRequests.sort()
-            request = self._periodicRequests[0]
-
-            t = time.time()
-
-            if request.nextFire>t:
-                break
-
-            attempts = self._processRequest(request, t, attempts)
-
-        while self._connectionRequested and self._requests:
-            request = self._requests[0]
-            del self._requests[0]
-
-            attempts = self._processRequest(request, None, attempts)
-
-        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
-
-    @staticmethod
-    def _appendHotkeyData(data, offset, hotkey):
-        """Append the data for the given hotkey to the given array, that is
-        intended to be passed to requestWrite call on the handler."""
-        data.append((offset + 0, "b", ord(hotkey.key)))
-
-        modifiers = 0
-        if hotkey.ctrl: modifiers |= 0x02
-        if hotkey.shift: modifiers |= 0x01
-        data.append((offset + 1, "b", modifiers))
-
-        data.append((offset + 2, "b", 0))
-
-        data.append((offset + 3, "b", 0))
-
-    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._fsType = None
-        self._aircraft = None
-
-        self._handler = Handler(self,
-                                connectAttempts = connectAttempts,
-                                connectInterval = connectInterval)
-        self._connectionListener = connectionListener
-        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._hotkeyLock = threading.Lock()
-        self._hotkeys = None
-        self._hotkeySetID = 0
-        self._hotkeySetGeneration = 0
-        self._hotkeyOffets = None
-        self._hotkeyRequestID = None
-        self._hotkeyCallback = None
-
-        self._latin1decoder = codecs.getdecoder("iso-8859-1")
-        self._fuelCallback = None
-
-    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."""
-
-        print "fsuipc.Simulator.sendMessage:", message
-
-        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, callback):
-        """Get the fuel information for the current model.
-
-        The callback will be called with a list of triplets with the following
-        items:
-        - the fuel tank identifier
-        - the current weight of the fuel in the tank (in kgs)
-        - the current total capacity of the tank (in kgs)."""
-        if self._aircraftModel is None:
-            self._fuelCallback = callback
-        else:
-            self._aircraftModel.getFuel(self._handler, 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."""
-        if self._aircraftModel is not None:
-            self._aircraftModel.setFuelLevel(self._handler, levels)
-
-    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 listenHotkeys(self, hotkeys, callback):
-        """Start listening to the given hotkeys.
-
-        callback is function expecting two arguments:
-        - the ID of the hotkey set as returned by this function,
-        - the list of the indexes of the hotkeys that were pressed."""
-        with self._hotkeyLock:
-            assert self._hotkeys is None
-
-            self._hotkeys = hotkeys
-            self._hotkeySetID += 1
-            self._hotkeySetGeneration = 0
-            self._hotkeyCallback = callback
-
-            self._handler.requestRead([(0x320c, "u")],
-                                      self._handleNumHotkeys,
-                                      (self._hotkeySetID,
-                                       self._hotkeySetGeneration))
-
-            return self._hotkeySetID
-
-    def clearHotkeys(self):
-        """Clear the current hotkey set.
-
-        Note that it is possible, that the callback function set either
-        previously or after calling this function by listenHotkeys() will be
-        called with data from the previous hotkey set.
-
-        Therefore it is recommended to store the hotkey set ID somewhere and
-        check that in the callback function. Right before calling
-        clearHotkeys(), this stored ID should be cleared so that the check
-        fails for sure."""
-        with self._hotkeyLock:
-            if self._hotkeys is not None:
-                self._hotkeys = None
-                self._hotkeySetID += 1
-                self._hotkeyCallback = None
-                self._clearHotkeyRequest()
-
-    def disconnect(self, closingMessage = None, duration = 3):
-        """Disconnect from the simulator."""
-        assert not self._monitoringRequested
-
-        print "fsuipc.Simulator.disconnect", closingMessage, duration
-
-        self._stopNormal()
-        self.clearHotkeys()
-        if closingMessage is None:
-            self._handler.disconnect()
-        else:
-            self.sendMessage(closingMessage, duration = duration,
-                             _disconnect = True)
-
-    def connected(self, fsType, descriptor):
-        """Called when a connection has been established to the flight
-        simulator of the given type."""
-        self._fsType = fsType
-        with self._hotkeyLock:
-            if self._hotkeys is not None:
-                self._hotkeySetGeneration += 1
-
-                self._handler.requestRead([(0x320c, "u")],
-                                          self._handleNumHotkeys,
-                                          (self._hotkeySetID,
-                                           self._hotkeySetGeneration))
-        self._connectionListener.connected(fsType, descriptor)
-
-    def connectionFailed(self):
-        """Called when the connection could not be established."""
-        with self._hotkeyLock:
-            self._clearHotkeyRequest()
-        self._connectionListener.connectionFailed()
-
-    def disconnected(self):
-        """Called when a connection to the flight simulator has been broken."""
-        with self._hotkeyLock:
-            self._clearHotkeyRequest()
-        self._connectionListener.disconnected()
-
-    def _startDefaultNormal(self):
-        """Start the default normal periodic request."""
-        assert self._normalRequestID is None
-        self._normalRequestID = \
-             self._handler.requestPeriodicRead(1.0,
-                                               Simulator.normalData,
-                                               self._handleNormal,
-                                               validator = self._validateNormal)
-
-    def _stopNormal(self):
-        """Stop the normal period request."""
-        assert self._normalRequestID is not None
-        self._handler.clearPeriodic(self._normalRequestID)
-        self._normalRequestID = None
-        self._monitoring = False
-
-    def _validateNormal(self, data, extra):
-        """Validate the normal data."""
-        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
-
-    def _handleNormal(self, data, extra):
-        """Handle the reply to the normal request.
-
-        At the beginning the result consists the data for normalData. When
-        monitoring is started, it contains the result also for the
-        aircraft-specific values.
-        """
-        timestamp = Simulator._getTimestamp(data)
-
-        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
-        if self._fuelCallback is not None:
-            self._aircraftModel.getFuel(self._handler, self._fuelCallback)
-            self._fuelCallback = None
-
-        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."""
-        name = self._latin1decoder(name)[0]
-        airPath = self._latin1decoder(airPath)[0]
-
-        aircraftName = (name, airPath)
-        if aircraftName==self._aircraftName:
-            return False
-
-        print "fsuipc.Simulator: new aircraft name and air file path: %s, %s" % \
-              (name, airPath)
-
-        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, name, self._aircraftModel.name)
-
-        return needNew
-
-    def _setAircraftModel(self, model):
-        """Set a new aircraft model.
-
-        It will be queried for the data to monitor and the monitoring request
-        will be replaced by a new one."""
-        self._aircraftModel = model
-
-        if self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-
-    def _startMonitoring(self):
-        """Start monitoring with the current aircraft model."""
-        data = Simulator.normalData[:]
-        self._aircraftModel.addMonitoringData(data, self._fsType)
-
-        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 _handleNumHotkeys(self, data, (id, generation)):
-        """Handle the result of the query of the number of hotkeys"""
-        with self._hotkeyLock:
-            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
-                numHotkeys = data[0]
-                print "fsuipc.Simulator._handleNumHotkeys: numHotkeys:", numHotkeys
-                data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]
-                self._handler.requestRead(data, self._handleHotkeyTable,
-                                          (id, generation))
-
-    def _setupHotkeys(self, data):
-        """Setup the hiven hotkeys and return the data to be written.
-
-        If there were hotkeys set previously, they are reused as much as
-        possible. Any of them not reused will be cleared."""
-        hotkeys = self._hotkeys
-        numHotkeys = len(hotkeys)
-
-        oldHotkeyOffsets = set([] if self._hotkeyOffets is None else
-                               self._hotkeyOffets)
-
-        self._hotkeyOffets = []
-        numOffsets = 0
-
-        while oldHotkeyOffsets:
-            offset = oldHotkeyOffsets.pop()
-            self._hotkeyOffets.append(offset)
-            numOffsets += 1
-
-            if numOffsets>=numHotkeys:
-                break
-
-        for i in range(0, len(data)):
-            if numOffsets>=numHotkeys:
-                break
-
-            if data[i]==0:
-                self._hotkeyOffets.append(0x3210 + i*4)
-                numOffsets += 1
-
-        writeData = []
-        for i in range(0, numOffsets):
-            Simulator._appendHotkeyData(writeData,
-                                        self._hotkeyOffets[i],
-                                        hotkeys[i])
-
-        for offset in oldHotkeyOffsets:
-            writeData.append((offset, "u", long(0)))
-
-        return writeData
-
-    def _handleHotkeyTable(self, data, (id, generation)):
-        """Handle the result of the query of the hotkey table."""
-        with self._hotkeyLock:
-            if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
-                writeData = self._setupHotkeys(data)
-                self._handler.requestWrite(writeData,
-                                           self._handleHotkeysWritten,
-                                           (id, generation))
-
-    def _handleHotkeysWritten(self, success, (id, generation)):
-        """Handle the result of the hotkeys having been written."""
-        with self._hotkeyLock:
-            if success and id==self._hotkeySetID and \
-            generation==self._hotkeySetGeneration:
-                data = [(offset + 3, "b") for offset in self._hotkeyOffets]
-
-                self._hotkeyRequestID = \
-                    self._handler.requestPeriodicRead(0.5, data,
-                                                      self._handleHotkeys,
-                                                      (id, generation))
-
-    def _handleHotkeys(self, data, (id, generation)):
-        """Handle the hotkeys."""
-        with self._hotkeyLock:
-            if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
-                return
-
-            callback = self._hotkeyCallback
-            offsets = self._hotkeyOffets
-
-        hotkeysPressed = []
-        for i in range(0, len(data)):
-            if data[i]!=0:
-                hotkeysPressed.append(i)
-
-        if hotkeysPressed:
-            data = []
-            for index in hotkeysPressed:
-                data.append((offsets[index]+3, "b", int(0)))
-            self._handler.requestWrite(data, self._handleHotkeysCleared)
-
-            callback(id, hotkeysPressed)
-
-    def _handleHotkeysCleared(self, sucess, extra):
-        """Callback for the hotkey-clearing write request."""
-
-    def _clearHotkeyRequest(self):
-        """Clear the hotkey request in the handler if there is any."""
-        if self._hotkeyRequestID is not None:
-            self._handler.clearPeriodic(self._hotkeyRequestID)
-            self._hotkeyRequestID = None
-
-#------------------------------------------------------------------------------
-
-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"),
-                      ("flapsAxis", 0x3414, "H"),
-                      ("flapsIncrement", 0x3bfa, "H"),
-                      ("lights", 0x0d0c, "H"),
-                      ("pitot", 0x029c, "b"),
-                      ("parking", 0x0bc8, "H"),
-                      ("gearControl", 0x0be8, "d"),
-                      ("noseGear", 0x0bec, "d"),
-                      ("spoilersArmed", 0x0bcc, "d"),
-                      ("spoilers", 0x0bd0, "d"),
-                      ("altimeter", 0x0330, "H"),
-                      ("qnh", 0x0ec6, "H"),
-                      ("nav1", 0x0350, "H"),
-                      ("nav1_obs", 0x0c4e, "H"),
-                      ("nav2", 0x0352, "H"),
-                      ("nav2_obs", 0x0c5e, "H"),
-                      ("adf1_main", 0x034c, "H"),
-                      ("adf1_ext", 0x0356, "H"),
-                      ("adf2_main", 0x02d4, "H"),
-                      ("adf2_ext", 0x02d6, "H"),
-                      ("squawk", 0x0354, "H"),
-                      ("windSpeed", 0x0e90, "H"),
-                      ("windDirection", 0x0e92, "H"),
-                      ("visibility", 0x0e8a, "H"),
-                      ("cog", 0x2ef8, "f"),
-                      ("xpdrC", 0x7b91, "b"),
-                      ("apMaster", 0x07bc, "d"),
-                      ("apHeadingHold", 0x07c8, "d"),
-                      ("apHeading", 0x07cc, "H"),
-                      ("apAltitudeHold", 0x07d0, "d"),
-                      ("apAltitude", 0x07d4, "u"),
-                      ("elevatorTrim", 0x2ea0, "f"),
-                      ("eng1DeIce", 0x08b2, "H"),
-                      ("eng2DeIce", 0x094a, "H"),
-                      ("propDeIce", 0x337c, "b"),
-                      ("structDeIce", 0x337d, "b")]
-
-    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]
-
-    @staticmethod
-    def convertADFFrequency(main, ext):
-        """Convert the given ADF frequency data to a string."""
-        mainBCD = AircraftModel.convertBCD(main, 4)
-        extBCD = AircraftModel.convertBCD(ext, 4)
-
-        return (extBCD[1] if extBCD[1]!="0" else "") + \
-               mainBCD[1:] + "." + extBCD[3]
-
-    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
-        self._xpdrReliable = False
-        self._flapsSet = -1
-
-    @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, fsType):
-        """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]
-        if state.flapsSet != self._flapsSet:
-            print "flapsControl: %d, flapsLeft: %d, flapsRight: %d, flapsAxis: %d, flapsIncrement: %d, flapsSet: %d, numNotchesM1: %d" % \
-                  (flapsControl, data[self._monidx_flapsLeft],
-                   data[self._monidx_flapsRight], data[self._monidx_flapsAxis],
-                   data[self._monidx_flapsIncrement], state.flapsSet, numNotchesM1)
-            self._flapsSet = state.flapsSet
-
-        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.gearControlDown = data[self._monidx_gearControl]==16383
-        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.altimeterReliable = True
-        state.qnh = data[self._monidx_qnh] / 16.0
-
-        state.ils = None
-        state.ils_obs = None
-        state.ils_manual = False
-        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
-        state.nav1_obs = data[self._monidx_nav1_obs]
-        state.nav1_manual = True
-        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
-        state.nav2_obs = data[self._monidx_nav2_obs]
-        state.nav2_manual = True
-        state.adf1 = \
-            AircraftModel.convertADFFrequency(data[self._monidx_adf1_main],
-                                              data[self._monidx_adf1_ext])
-        state.adf2 = \
-            AircraftModel.convertADFFrequency(data[self._monidx_adf2_main],
-                                              data[self._monidx_adf2_ext])
-
-        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
-
-        state.cog = data[self._monidx_cog]
-
-        if not self._xpdrReliable:
-            self._xpdrReliable = data[self._monidx_xpdrC]!=0
-
-        state.xpdrC = data[self._monidx_xpdrC]!=1 \
-                      if self._xpdrReliable else None
-        state.autoXPDR = False
-
-        state.apMaster = data[self._monidx_apMaster]!=0
-        state.apHeadingHold = data[self._monidx_apHeadingHold]!=0
-        state.apHeading = data[self._monidx_apHeading] * 360.0 / 65536.0
-        state.apAltitudeHold = data[self._monidx_apAltitudeHold]!=0
-        state.apAltitude = data[self._monidx_apAltitude] / \
-                           const.FEETTOMETRES / 65536.0
-
-
-        state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
-
-        state.antiIceOn = data[self._monidx_eng1DeIce]!=0 or \
-                          data[self._monidx_eng2DeIce]!=0 or \
-                          data[self._monidx_propDeIce]!=0 or \
-                          data[self._monidx_structDeIce]!=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, fsType):
-        """Add the model-specific monitoring data to the given array."""
-        super(GenericAircraftModel, self).addMonitoringData(data, fsType)
-
-        self._fuelStartIndex = self._addFuelOffsets(data, "_monidx_fuelWeight")
-
-        self._engineStartIndex = len(data)
-        for i in range(0, self._numEngines):
-            self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
-            if self._isN1:
-                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
-            else:
-                self._addOffsetWithIndexMember(data, 0x0898 + i * 0x98, "H")  # RPM
-                self._addOffsetWithIndexMember(data, 0x08c8 + i * 0x98, "H")  # RPM scaler
-
-    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)
-
-        (state.fuel, state.totalFuel) = \
-            self._convertFuelData(data, index = self._monidx_fuelWeight)
-
-        state.n1 = [] if self._isN1 else None
-        state.rpm = None if self._isN1 else []
-        itemsPerEngine = 2 if self._isN1 else 3
-
-        state.reverser = []
-        for i in range(self._engineStartIndex,
-                       self._engineStartIndex +
-                       itemsPerEngine*self._numEngines,
-                       itemsPerEngine):
-            state.reverser.append(data[i]<0)
-            if self._isN1:
-                state.n1.append(data[i+1])
-            else:
-                state.rpm.append(data[i+1] * data[i+2]/65536.0)
-
-        return state
-
-    def getFuel(self, handler, callback):
-        """Get the fuel information for this model.
-
-        See Simulator.getFuel for more information. This
-        implementation simply queries the fuel tanks given to the
-        constructor."""
-        data = []
-        self._addFuelOffsets(data)
-
-        handler.requestRead(data, self._handleFuelRetrieved,
-                            extra = callback)
-
-    def setFuelLevel(self, handler, levels):
-        """Set the fuel level.
-
-        See the description of Simulator.setFuelLevel. This
-        implementation simply sets the fuel tanks as given."""
-        data = []
-        for (tank, level) in levels:
-            offset = _tank2offset[tank]
-            value = long(level * 128.0 * 65536.0)
-            data.append( (offset, "u", value) )
-
-        handler.requestWrite(data, self._handleFuelWritten)
-
-    def _addFuelOffsets(self, data, weightIndexName = None):
-        """Add the fuel offsets to the given data array.
-
-        If weightIndexName is not None, it will be the name of the
-        fuel weight index.
-
-        Returns the index of the first fuel tank's data."""
-        self._addOffsetWithIndexMember(data, 0x0af4, "H", weightIndexName)
-
-        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
-
-        return fuelStartIndex
-
-    def _convertFuelData(self, data, index = 0, addCapacities = False):
-        """Convert the given data into a fuel info list.
-
-        The list consists of two or three-tuples of the following
-        items:
-        - the fuel tank ID,
-        - the amount of the fuel in kg,
-        - if addCapacities is True, the total capacity of the tank."""
-        fuelWeight = data[index] / 256.0
-        index += 1
-
-        result = []
-        totalFuel = 0
-        for fuelTank in self._fuelTanks:
-            capacity = data[index+1] * fuelWeight * const.LBSTOKG
-            if capacity>=1.0:
-                amount = data[index] * capacity / 128.0 / 65536.0
-
-                result.append( (fuelTank, amount, capacity) if addCapacities
-                               else (fuelTank, amount))
-                totalFuel += amount
-            index += 2
-
-        return (result, totalFuel)
-
-    def _handleFuelRetrieved(self, data, callback):
-        """Callback for a fuel retrieval request."""
-        (fuelData, _totalFuel) = self._convertFuelData(data,
-                                                       addCapacities = True)
-        callback(fuelData)
-
-    def _handleFuelWritten(self, success, extra):
-        """Callback for a fuel setting request."""
-        pass
-
-#------------------------------------------------------------------------------
-
-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."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(B737Model, self). \
-            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
-                     fuelTanks = B737Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Boeing 737"
-
-    # Note: the function below should be enabled if testing the speed-based
-    # takeoff on Linux
-    # def getAircraftState(self, aircraft, timestamp, data):
-    #     """Get the aircraft state.
-
-    #     Get it from the parent, and then check some PMDG-specific stuff."""
-    #     state = super(B737Model, self).getAircraftState(aircraft,
-    #                                                     timestamp,
-    #                                                     data)
-    #     state.strobeLightsOn = None
-    #     state.xpdrC = None
-
-    #     return state
-
-#------------------------------------------------------------------------------
-
-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,
-                                 const.AIRCRAFT_B738C] 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(X)"
-
-    def addMonitoringData(self, data, fsType):
-        """Add the model-specific monitoring data to the given array."""
-        self._fsType = fsType
-
-        super(PMDGBoeing737NGModel, self).addMonitoringData(data, fsType)
-
-        if fsType==const.SIM_MSFSX or fsType==const.SIM_P3D:
-            print "%s detected, adding PMDG 737 NGX-specific offsets" % \
-                  ("FSX" if fsType==const.SIM_MSFSX else "P3D",)
-            self._addOffsetWithIndexMember(data, 0x6500, "b",
-                                           "_pmdgidx_lts_positionsw")
-            self._addOffsetWithIndexMember(data, 0x6545, "b", "_pmdgidx_cmda")
-            self._addOffsetWithIndexMember(data, 0x653f, "b", "_pmdgidx_aphdgsel")
-            self._addOffsetWithIndexMember(data, 0x6543, "b", "_pmdgidx_apalthold")
-            self._addOffsetWithIndexMember(data, 0x652c, "H", "_pmdgidx_aphdg")
-            self._addOffsetWithIndexMember(data, 0x652e, "H", "_pmdgidx_apalt")
-            self._addOffsetWithIndexMember(data, 0x65cd, "b", "_pmdgidx_xpdr")
-        else:
-            print "FS9 detected, adding PMDG 737 NG-specific offsets"
-            self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
-            self._addOffsetWithIndexMember(data, 0x6216, "b", "_pmdgidx_xpdr")
-            self._addOffsetWithIndexMember(data, 0x6227, "b", "_pmdgidx_ap")
-            self._addOffsetWithIndexMember(data, 0x6228, "b", "_pmdgidx_aphdgsel")
-            self._addOffsetWithIndexMember(data, 0x622a, "b", "_pmdgidx_apalthold")
-            self._addOffsetWithIndexMember(data, 0x622c, "H", "_pmdgidx_aphdg")
-            self._addOffsetWithIndexMember(data, 0x622e, "H", "_pmdgidx_apalt")
-
-    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 self._fsType==const.SIM_MSFS9:
-            if data[self._pmdgidx_switches]&0x01==0x01:
-                state.altimeter = 1013.25
-            state.apMaster = data[self._pmdgidx_ap]&0x02==0x02
-            state.apHeadingHold = data[self._pmdgidx_aphdgsel]==2
-            apalthold = data[self._pmdgidx_apalthold]
-            state.apAltitudeHold = apalthold>=3 and apalthold<=6
-            state.xpdrC = data[self._pmdgidx_xpdr]==4
-
-            # Uncomment the following to test the speed-based takeoff
-            # state.strobeLightsOn = None
-            # state.xpdrC = None
-        else:
-            state.apMaster = data[self._pmdgidx_cmda]!=0
-            state.apHeadingHold = data[self._pmdgidx_aphdgsel]!=0
-            state.apAltitudeHold = data[self._pmdgidx_apalthold]!=0
-
-            # state.strobeLightsOn = data[self._pmdgidx_lts_positionsw]==0x02
-            # state.xpdrC = data[self._pmdgidx_xpdr]==4
-            state.strobeLightsOn = None
-            state.xpdrC = None
-
-        state.apHeading = data[self._pmdgidx_aphdg]
-        state.apAltitude = data[self._pmdgidx_apalt]
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class B767Model(GenericAircraftModel):
-    """Generic model for the Boeing 767 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(B767Model, self). \
-            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
-                     fuelTanks = B767Model.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."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(DH8DModel, self). \
-            __init__(flapsNotches = [0, 5, 10, 15, 35],
-                     fuelTanks = DH8DModel.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 addMonitoringData(self, data, fsType):
-        """Add the model-specific monitoring data to the given array."""
-        super(DreamwingsDH8DModel, self).addMonitoringData(data, fsType)
-
-        self._addOffsetWithIndexMember(data, 0x132c, "d", "_dwdh8d_navgps")
-
-    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)
-        if data[self._dwdh8d_navgps]==1:
-            state.apHeading = None
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class MajesticDH8DModel(DH8DModel):
-    """Model handler for the Majestic 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("MJC8Q400")!=-1 or \
-             airPath.lower().find("mjc8q400") or \
-             airPath.lower().find("mjc8q4.air"))
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Majestic Bombardier Dash 8-Q400"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then clear the anti-collision and landing
-        lights."""
-        state = super(MajesticDH8DModel, self).getAircraftState(aircraft,
-                                                                timestamp,
-                                                                data)
-        state.antiCollisionLightsOn = None
-        state.strobeLightsOn = None
-        state.pitotHeatOn = None
-
-        # G-load seems to be offset by -1.0 (i.e a value of 0 seem to mean
-        # a G-load of 1.0)
-        state.gLoad += 1.0
-
-        # None of the gear values seem to work correctly
-        state.gearsDown = state.gearControlDown
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class CRJ2Model(GenericAircraftModel):
-    """Generic model for the Bombardier CRJ-200 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(CRJ2Model, self). \
-            __init__(flapsNotches = [0, 8, 20, 30, 45],
-                     fuelTanks = CRJ2Model.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."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(F70Model, self). \
-            __init__(flapsNotches = [0, 8, 15, 25, 42],
-                     fuelTanks = F70Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Fokker 70"
-
-#------------------------------------------------------------------------------
-
-class DAF70Model(F70Model):
-    """Model for the Digital Aviation F70 implementation on FS9."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type == const.AIRCRAFT_F70 and \
-               (airPath.endswith("fokker70_2k4_v4.1.air") or
-                airPath.endswith("fokker70_2k4_v4.3.air"))
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Digital Aviation Fokker 70"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then invert the pitot heat state."""
-        state = super(DAF70Model, self).getAircraftState(aircraft,
-                                                         timestamp,
-                                                         data)
-        state.navLightsOn = None
-        state.landingLightsOn = None
-
-        state.altimeterReliable = False
-
-        state.ils = state.nav1
-        state.ils_obs = state.nav1_obs
-        state.ils_manual = state.nav1_manual
-
-        state.nav1 = state.nav2
-        state.nav1_obs = state.nav2_obs
-        state.nav1_manual = aircraft.flight.stage!=const.STAGE_CRUISE
-
-        state.nav2 = None
-        state.nav2_obs = None
-        state.nav2_manual = False
-
-        state.autoXPDR = True
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class DC3Model(GenericAircraftModel):
-    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT]
-    # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-    #              const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-
-    def __init__(self):
-        """Construct the model."""
-        super(DC3Model, self). \
-            __init__(flapsNotches = [0, 15, 30, 45],
-                     fuelTanks = DC3Model.fuelTanks,
-                     numEngines = 2, isN1 = False)
-        self._leftLevel = 0.0
-        self._rightLevel = 0.0
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Lisunov Li-2 (DC-3)"
-
-    def _convertFuelData(self, data, index = 0, addCapacities = False):
-        """Convert the given data into a fuel info list.
-
-        It assumes to receive the 3 fuel tanks as seen above (left,
-        centre and right) and converts it to left aux, left, right,
-        and right aux. The amount in the left tank goes into left aux,
-        the amount of the right tank goes into right aux and the
-        amount of the centre tank goes into the left and right tanks
-        evenly distributed."""
-        (rawFuelData, totalFuel) = \
-            super(DC3Model, self)._convertFuelData(data, index, addCapacities)
-
-        centreAmount = rawFuelData[1][1]
-        if addCapacities:
-            centreCapacity = rawFuelData[1][2]
-            self._leftLevel = self._rightLevel = \
-                centreAmount / centreCapacity / 2.0
-            fuelData = [(const.FUELTANK_LEFT_AUX,
-                         rawFuelData[0][1], rawFuelData[0][2]),
-                        (const.FUELTANK_LEFT,
-                         centreAmount/2.0, centreCapacity/2.0),
-                        (const.FUELTANK_RIGHT,
-                         centreAmount/2.0, centreCapacity/2.0),
-                        (const.FUELTANK_RIGHT_AUX,
-                         rawFuelData[2][1], rawFuelData[2][2])]
-        else:
-            fuelData = [(const.FUELTANK_LEFT_AUX, rawFuelData[0][1]),
-                        (const.FUELTANK_LEFT, centreAmount/2.0),
-                        (const.FUELTANK_RIGHT, centreAmount/2.0),
-                        (const.FUELTANK_RIGHT_AUX, rawFuelData[2][1])]
-
-        return (fuelData, totalFuel)
-
-    def setFuelLevel(self, handler, levels):
-        """Set the fuel level.
-
-        See the description of Simulator.setFuelLevel. This
-        implementation assumes to get the four-tank representation,
-        as returned by getFuel()."""
-        leftLevel = None
-        centreLevel = None
-        rightLevel = None
-
-        for (tank, level) in levels:
-            if tank==const.FUELTANK_LEFT_AUX:
-                leftLevel = level if leftLevel is None else (leftLevel + level)
-            elif tank==const.FUELTANK_LEFT:
-                level /= 2.0
-                centreLevel = (self._rightLevel + level) \
-                              if centreLevel is None else (centreLevel + level)
-                self._leftLevel = level
-            elif tank==const.FUELTANK_RIGHT:
-                level /= 2.0
-                centreLevel = (self._leftLevel + level) \
-                              if centreLevel is None else (centreLevel + level)
-                self._rightLevel = level
-            elif tank==const.FUELTANK_RIGHT_AUX:
-                rightLevel = level if rightLevel is None \
-                             else (rightLevel + level)
-
-        levels = []
-        if leftLevel is not None: levels.append((const.FUELTANK_LEFT,
-                                                 leftLevel))
-        if centreLevel is not None: levels.append((const.FUELTANK_CENTRE,
-                                                   centreLevel))
-        if rightLevel is not None: levels.append((const.FUELTANK_RIGHT,
-                                                 rightLevel))
-
-        super(DC3Model, self).setFuelLevel(handler, levels)
-
-#------------------------------------------------------------------------------
-
-class T134Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
-                 const.FUELTANK_LEFT_AUX,
-                 const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT_AUX,
-                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
-
-    def __init__(self):
-        """Construct the model."""
-        super(T134Model, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelTanks = T134Model.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."""
-    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
-                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-
-    def __init__(self):
-        """Construct the model."""
-        super(T154Model, self). \
-            __init__(flapsNotches = [0, 15, 28, 45],
-                     fuelTanks = T154Model.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 PTT154Model(T154Model):
-    """Project Tupolev Tu-154."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        print "PTT154Model.doesHandle", aircraft.type, name, airPath
-        return aircraft.type==const.AIRCRAFT_T154 and \
-               (name.find("Tu-154")!=-1 or name.find("Tu154B")!=-1) and \
-               os.path.basename(airPath).startswith("154b_")
-
-    def __init__(self):
-        """Construct the model."""
-        super(PTT154Model, self).__init__()
-        self._fsType = None
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Project Tupolev Tu-154"
-
-    def addMonitoringData(self, data, fsType):
-        """Add the model-specific monitoring data to the given array.
-
-        It only stores the flight simulator type."""
-        self._fsType = fsType
-
-        super(PTT154Model, self).addMonitoringData(data, fsType)
-
-    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(PTT154Model, self).getAircraftState(aircraft, timestamp, data)
-
-        if self._fsType==const.SIM_MSFSX or self._fsType==const.SIM_P3D:
-            state.xpdrC = None
-
-        return state
-
-
-#------------------------------------------------------------------------------
-
-class YK40Model(GenericAircraftModel):
-    """Generic model for the Yakovlev Yak-40 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(YK40Model, self). \
-            __init__(flapsNotches = [0, 20, 35],
-                     fuelTanks = YK40Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Yakovlev Yak-40"
-
-#------------------------------------------------------------------------------
-
-class B462Model(GenericAircraftModel):
-    """Generic model for the British Aerospace BAe 146-200 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(B462Model, self). \
-            __init__(flapsNotches = [0, 18, 24, 30, 33],
-                     fuelTanks = B462Model.fuelTanks,
-                     numEngines = 4)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic British Aerospace 146"
-
-    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(B462Model, self).getAircraftState(aircraft, timestamp, data)
-        state.reverser = []
-        return state
-
-#------------------------------------------------------------------------------
-
-_genericModels = { const.AIRCRAFT_B736  : B737Model,
-                   const.AIRCRAFT_B737  : B737Model,
-                   const.AIRCRAFT_B738  : B737Model,
-                   const.AIRCRAFT_B738C : 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  : CRJ2Model,
-                   const.AIRCRAFT_F70   : F70Model,
-                   const.AIRCRAFT_DC3   : DC3Model,
-                   const.AIRCRAFT_T134  : T134Model,
-                   const.AIRCRAFT_T154  : T154Model,
-                   const.AIRCRAFT_YK40  : YK40Model,
-                   const.AIRCRAFT_B462  : B462Model }
-
-#------------------------------------------------------------------------------
-
-AircraftModel.registerSpecial(PMDGBoeing737NGModel)
-AircraftModel.registerSpecial(DreamwingsDH8DModel)
-AircraftModel.registerSpecial(MajesticDH8DModel)
-AircraftModel.registerSpecial(DAF70Model)
-AircraftModel.registerSpecial(PTT154Model)
-
-#------------------------------------------------------------------------------
Index: src/mlx/gates.py
===================================================================
--- src/mlx/gates.py	(revision 619)
+++ src/mlx/gates.py	(revision 619)
@@ -0,0 +1,217 @@
+#--------------------------------------------------------------------------------------
+
+## @package mlx.gates
+#
+# The module to handle the LHBP gate information.
+#
+
+#--------------------------------------------------------------------------------------
+
+class Gate(object):
+    """Information about a gate."""
+    def __init__(self, number, terminal, type,
+                 availableFn = None):
+        """Construct the gate with the given information.
+
+        number is the gate's number as a string (as it can contain letters).
+        terminal is the terminal the gate belongs to (not used currently).
+        type is the gate's type: G for gate, S for stand.
+        availableFn is a function that can determine if the gate is available based on
+        the statuses of other gates. Its arguments are:
+        - a collection of the gates, and
+        - a set of occupied gate numbers."""
+        self._number = number
+        self._terminal = terminal
+        self._type = type
+        self._availableFn = availableFn
+
+    @property
+    def number(self):
+        """Get the number of the gate."""
+        return self._number
+
+    def isAvailable(self, gates, occupiedGateNumbers):
+        """Determine if this gate is available given the set of gates and
+        occupied gate numbers."""
+        if self._number in occupiedGateNumbers:
+            return False
+        return True if self._availableFn is None else \
+               self._availableFn(gates, occupiedGateNumbers)
+
+#--------------------------------------------------------------------------------------
+
+class Gates(object):
+    """A collection of gates."""
+    # Display info type: a gate (number)
+    DISPLAY_GATE=1
+
+    # Display info type: a space
+    DISPLAY_SPACE=2
+
+    # Display info type: a new column
+    DISPLAY_NEW_COLUMN=3
+
+    def __init__(self):
+        """Construct the gate collection."""
+        self._gates = []
+        self._displayInfos = []
+        self._numColumns = 1
+        self._numRows = 0
+        self._numRowsInColumn = 0
+
+    @property
+    def gates(self):
+        """Get an iterator over the gates."""
+        return iter(self._gates)
+
+    @property
+    def displayInfos(self):
+        """Get an iterator over the display info tuples.
+
+        Each tuple consists of
+        - a type (one of the DISPLAY_XXX constants), and
+        - an additional data (the gate for DISPLAY_GATE, None for others)."""
+        return iter(self._displayInfos)
+
+    @property
+    def numRows(self):
+        """Get the number of rows."""
+        return self._numRows
+
+    @property
+    def numColumns(self):
+        """Get the number of columns."""
+        return self._numColumns
+
+    def add(self, gate):
+        """Add a gate to the collection."""
+        self._gates.append(gate)
+        self._displayInfos.append((Gates.DISPLAY_GATE, gate))
+        self._addRow()
+
+    def addSpace(self):
+        """Add a space between subsequent gates."""
+        self._displayInfos.append((Gates.DISPLAY_SPACE, None))
+        self._addRow()
+
+    def addNewColumn(self):
+        """Start a new column of gates."""
+        self._displayInfos.append((Gates.DISPLAY_NEW_COLUMN, None))
+        self._numRowsInColumn = 0
+        self._numColumns += 1
+
+    def _addRow(self):
+        """Add a new row."""
+        self._numRowsInColumn += 1
+        if self._numRowsInColumn > self._numRows:
+            self._numRows = self._numRowsInColumn
+
+#--------------------------------------------------------------------------------------
+
+def availableIf(occupiedGateNumbers, othersAvailable = []):
+    """Determine if a gate is available.
+
+    othersAvailable is a list of numbers of gates, that must be available so
+    that the one we are considering is available."""
+    for otherNumber in othersAvailable:
+        if otherNumber in occupiedGateNumbers:
+            return False
+    return True
+
+#--------------------------------------------------------------------------------------
+
+def getAvilableIf(othersAvailable = []):
+    """Get a function that determines if a gate is available based on the
+    statuses of other gates."""
+    return lambda gates, occupiedGateNumbers: availableIf(occupiedGateNumbers,
+                                                          othersAvailable =
+                                                          othersAvailable)
+
+#--------------------------------------------------------------------------------------
+
+# The gates at LHBP
+lhbpGates = Gates()
+
+lhbpGates.add(Gate("1", "1", "S"))
+lhbpGates.add(Gate("2", "1", "S"))
+lhbpGates.add(Gate("3", "1", "S"))
+lhbpGates.add(Gate("4", "1", "S"))
+lhbpGates.add(Gate("5", "1", "S"))
+lhbpGates.add(Gate("6", "1", "S"))
+lhbpGates.add(Gate("25", "1", "S"))
+lhbpGates.add(Gate("26", "1", "S"))
+lhbpGates.add(Gate("27", "1", "S"))
+lhbpGates.addSpace()
+
+lhbpGates.add(Gate("31", "2B", "G"))
+lhbpGates.add(Gate("32", "2B", "G"))
+lhbpGates.add(Gate("33", "2B", "G"))
+lhbpGates.addNewColumn()
+
+lhbpGates.add(Gate("34", "2B", "G"))
+lhbpGates.add(Gate("35", "2B", "G"))
+lhbpGates.add(Gate("36", "2B", "G"))
+lhbpGates.add(Gate("37", "2B", "G"))
+lhbpGates.add(Gate("38", "2B", "G"))
+lhbpGates.add(Gate("39", "2B", "G"))
+lhbpGates.addSpace()
+
+lhbpGates.add(Gate("42", "2A", "G"))
+lhbpGates.add(Gate("43", "2A", "G"))
+lhbpGates.add(Gate("44", "2A", "G"))
+lhbpGates.add(Gate("45", "2A", "G"))
+lhbpGates.addNewColumn()
+
+lhbpGates.add(Gate("107", "1", "S"))
+lhbpGates.add(Gate("108", "1", "S"))
+lhbpGates.add(Gate("109", "1", "S"))
+lhbpGates.add(Gate("R110", "1", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R111"])))
+lhbpGates.add(Gate("R111", "1", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R110", "R112"])))
+lhbpGates.add(Gate("R112", "1", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R111"])))
+lhbpGates.add(Gate("R113", "1", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R114"])))
+lhbpGates.add(Gate("R114", "1", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R113"])))
+lhbpGates.add(Gate("R115", "1", "S"))
+lhbpGates.add(Gate("R116", "1", "S"))
+lhbpGates.add(Gate("R117", "1", "S"))
+lhbpGates.addNewColumn()
+
+lhbpGates.add(Gate("R210", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
+lhbpGates.add(Gate("R211", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
+lhbpGates.add(Gate("R212", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
+lhbpGates.add(Gate("R212A", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R210", "R211", "R212"])))
+lhbpGates.addSpace()
+
+lhbpGates.add(Gate("R220", "2B", "S"))
+lhbpGates.add(Gate("R221", "2B", "S"))
+lhbpGates.add(Gate("R222", "2B", "S"))
+lhbpGates.add(Gate("R223", "2B", "S"))
+lhbpGates.addSpace()
+
+lhbpGates.add(Gate("R224", "2A", "R"))
+lhbpGates.add(Gate("R225", "2A", "S"))
+lhbpGates.add(Gate("R226", "2A", "S"))
+lhbpGates.add(Gate("R227", "2A", "S"))
+lhbpGates.addNewColumn()
+
+lhbpGates.add(Gate("R270", "2A", "S"))
+lhbpGates.add(Gate("R271", "2A", "S"))
+lhbpGates.add(Gate("R272", "2A", "S"))
+lhbpGates.add(Gate("R274", "2A", "S"))
+lhbpGates.add(Gate("R275", "2A", "S"))
+lhbpGates.add(Gate("R276", "2A", "S"))
+lhbpGates.add(Gate("R277", "2A", "S"))
+lhbpGates.add(Gate("R278", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R278A"])))
+lhbpGates.add(Gate("R278A", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R278", "R279"])))
+lhbpGates.add(Gate("R279", "2A", "S",
+                   availableFn = getAvilableIf(othersAvailable = ["R278A"])))
Index: src/mlx/gates.py
===================================================================
--- src/mlx/gates.py	(revision 619)
+++ 	(revision )
@@ -1,217 +1,0 @@
-#--------------------------------------------------------------------------------------
-
-## @package mlx.gates
-#
-# The module to handle the LHBP gate information.
-#
-
-#--------------------------------------------------------------------------------------
-
-class Gate(object):
-    """Information about a gate."""
-    def __init__(self, number, terminal, type,
-                 availableFn = None):
-        """Construct the gate with the given information.
-
-        number is the gate's number as a string (as it can contain letters).
-        terminal is the terminal the gate belongs to (not used currently).
-        type is the gate's type: G for gate, S for stand.
-        availableFn is a function that can determine if the gate is available based on
-        the statuses of other gates. Its arguments are:
-        - a collection of the gates, and
-        - a set of occupied gate numbers."""
-        self._number = number
-        self._terminal = terminal
-        self._type = type
-        self._availableFn = availableFn
-
-    @property
-    def number(self):
-        """Get the number of the gate."""
-        return self._number
-
-    def isAvailable(self, gates, occupiedGateNumbers):
-        """Determine if this gate is available given the set of gates and
-        occupied gate numbers."""
-        if self._number in occupiedGateNumbers:
-            return False
-        return True if self._availableFn is None else \
-               self._availableFn(gates, occupiedGateNumbers)
-
-#--------------------------------------------------------------------------------------
-
-class Gates(object):
-    """A collection of gates."""
-    # Display info type: a gate (number)
-    DISPLAY_GATE=1
-
-    # Display info type: a space
-    DISPLAY_SPACE=2
-
-    # Display info type: a new column
-    DISPLAY_NEW_COLUMN=3
-
-    def __init__(self):
-        """Construct the gate collection."""
-        self._gates = []
-        self._displayInfos = []
-        self._numColumns = 1
-        self._numRows = 0
-        self._numRowsInColumn = 0
-
-    @property
-    def gates(self):
-        """Get an iterator over the gates."""
-        return iter(self._gates)
-
-    @property
-    def displayInfos(self):
-        """Get an iterator over the display info tuples.
-
-        Each tuple consists of
-        - a type (one of the DISPLAY_XXX constants), and
-        - an additional data (the gate for DISPLAY_GATE, None for others)."""
-        return iter(self._displayInfos)
-
-    @property
-    def numRows(self):
-        """Get the number of rows."""
-        return self._numRows
-
-    @property
-    def numColumns(self):
-        """Get the number of columns."""
-        return self._numColumns
-
-    def add(self, gate):
-        """Add a gate to the collection."""
-        self._gates.append(gate)
-        self._displayInfos.append((Gates.DISPLAY_GATE, gate))
-        self._addRow()
-
-    def addSpace(self):
-        """Add a space between subsequent gates."""
-        self._displayInfos.append((Gates.DISPLAY_SPACE, None))
-        self._addRow()
-
-    def addNewColumn(self):
-        """Start a new column of gates."""
-        self._displayInfos.append((Gates.DISPLAY_NEW_COLUMN, None))
-        self._numRowsInColumn = 0
-        self._numColumns += 1
-
-    def _addRow(self):
-        """Add a new row."""
-        self._numRowsInColumn += 1
-        if self._numRowsInColumn > self._numRows:
-            self._numRows = self._numRowsInColumn
-
-#--------------------------------------------------------------------------------------
-
-def availableIf(occupiedGateNumbers, othersAvailable = []):
-    """Determine if a gate is available.
-
-    othersAvailable is a list of numbers of gates, that must be available so
-    that the one we are considering is available."""
-    for otherNumber in othersAvailable:
-        if otherNumber in occupiedGateNumbers:
-            return False
-    return True
-
-#--------------------------------------------------------------------------------------
-
-def getAvilableIf(othersAvailable = []):
-    """Get a function that determines if a gate is available based on the
-    statuses of other gates."""
-    return lambda gates, occupiedGateNumbers: availableIf(occupiedGateNumbers,
-                                                          othersAvailable =
-                                                          othersAvailable)
-
-#--------------------------------------------------------------------------------------
-
-# The gates at LHBP
-lhbpGates = Gates()
-
-lhbpGates.add(Gate("1", "1", "S"))
-lhbpGates.add(Gate("2", "1", "S"))
-lhbpGates.add(Gate("3", "1", "S"))
-lhbpGates.add(Gate("4", "1", "S"))
-lhbpGates.add(Gate("5", "1", "S"))
-lhbpGates.add(Gate("6", "1", "S"))
-lhbpGates.add(Gate("25", "1", "S"))
-lhbpGates.add(Gate("26", "1", "S"))
-lhbpGates.add(Gate("27", "1", "S"))
-lhbpGates.addSpace()
-
-lhbpGates.add(Gate("31", "2B", "G"))
-lhbpGates.add(Gate("32", "2B", "G"))
-lhbpGates.add(Gate("33", "2B", "G"))
-lhbpGates.addNewColumn()
-
-lhbpGates.add(Gate("34", "2B", "G"))
-lhbpGates.add(Gate("35", "2B", "G"))
-lhbpGates.add(Gate("36", "2B", "G"))
-lhbpGates.add(Gate("37", "2B", "G"))
-lhbpGates.add(Gate("38", "2B", "G"))
-lhbpGates.add(Gate("39", "2B", "G"))
-lhbpGates.addSpace()
-
-lhbpGates.add(Gate("42", "2A", "G"))
-lhbpGates.add(Gate("43", "2A", "G"))
-lhbpGates.add(Gate("44", "2A", "G"))
-lhbpGates.add(Gate("45", "2A", "G"))
-lhbpGates.addNewColumn()
-
-lhbpGates.add(Gate("107", "1", "S"))
-lhbpGates.add(Gate("108", "1", "S"))
-lhbpGates.add(Gate("109", "1", "S"))
-lhbpGates.add(Gate("R110", "1", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R111"])))
-lhbpGates.add(Gate("R111", "1", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R110", "R112"])))
-lhbpGates.add(Gate("R112", "1", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R111"])))
-lhbpGates.add(Gate("R113", "1", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R114"])))
-lhbpGates.add(Gate("R114", "1", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R113"])))
-lhbpGates.add(Gate("R115", "1", "S"))
-lhbpGates.add(Gate("R116", "1", "S"))
-lhbpGates.add(Gate("R117", "1", "S"))
-lhbpGates.addNewColumn()
-
-lhbpGates.add(Gate("R210", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
-lhbpGates.add(Gate("R211", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
-lhbpGates.add(Gate("R212", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R212A"])))
-lhbpGates.add(Gate("R212A", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R210", "R211", "R212"])))
-lhbpGates.addSpace()
-
-lhbpGates.add(Gate("R220", "2B", "S"))
-lhbpGates.add(Gate("R221", "2B", "S"))
-lhbpGates.add(Gate("R222", "2B", "S"))
-lhbpGates.add(Gate("R223", "2B", "S"))
-lhbpGates.addSpace()
-
-lhbpGates.add(Gate("R224", "2A", "R"))
-lhbpGates.add(Gate("R225", "2A", "S"))
-lhbpGates.add(Gate("R226", "2A", "S"))
-lhbpGates.add(Gate("R227", "2A", "S"))
-lhbpGates.addNewColumn()
-
-lhbpGates.add(Gate("R270", "2A", "S"))
-lhbpGates.add(Gate("R271", "2A", "S"))
-lhbpGates.add(Gate("R272", "2A", "S"))
-lhbpGates.add(Gate("R274", "2A", "S"))
-lhbpGates.add(Gate("R275", "2A", "S"))
-lhbpGates.add(Gate("R276", "2A", "S"))
-lhbpGates.add(Gate("R277", "2A", "S"))
-lhbpGates.add(Gate("R278", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R278A"])))
-lhbpGates.add(Gate("R278A", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R278", "R279"])))
-lhbpGates.add(Gate("R279", "2A", "S",
-                   availableFn = getAvilableIf(othersAvailable = ["R278A"])))
Index: src/mlx/gui/acars.py
===================================================================
--- src/mlx/gui/acars.py	(revision 649)
+++ src/mlx/gui/acars.py	(revision 649)
@@ -0,0 +1,36 @@
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+
+import cef
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.acars
+#
+# The ACARS tab.
+#
+# This module implements to \ref ACRS class, which displays the MAVA ACARS in a
+# browser window using CEF
+
+#------------------------------------------------------------------------------
+
+class ACARS(gtk.VBox):
+    """The flight info tab."""
+    # The URL of the ACARS map
+    URL = "http://virtualairlines.hu/acars2/show.html"
+
+    def __init__(self, gui):
+        """Construct the flight info tab."""
+        super(ACARS, self).__init__()
+        self._gui = gui
+
+    def start(self):
+        """Start the browser."""
+        container = cef.getContainer()
+
+        self.pack_start(container, True, True, 0)
+
+        cef.startInContainer(container, ACARS.URL)
Index: src/mlx/gui/acars.py
===================================================================
--- src/mlx/gui/acars.py	(revision 649)
+++ 	(revision )
@@ -1,36 +1,0 @@
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-
-import cef
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.acars
-#
-# The ACARS tab.
-#
-# This module implements to \ref ACRS class, which displays the MAVA ACARS in a
-# browser window using CEF
-
-#------------------------------------------------------------------------------
-
-class ACARS(gtk.VBox):
-    """The flight info tab."""
-    # The URL of the ACARS map
-    URL = "http://virtualairlines.hu/acars2/show.html"
-
-    def __init__(self, gui):
-        """Construct the flight info tab."""
-        super(ACARS, self).__init__()
-        self._gui = gui
-
-    def start(self):
-        """Start the browser."""
-        container = cef.getContainer()
-
-        self.pack_start(container, True, True, 0)
-
-        cef.startInContainer(container, ACARS.URL)
Index: src/mlx/gui/bugreport.py
===================================================================
--- src/mlx/gui/bugreport.py	(revision 492)
+++ src/mlx/gui/bugreport.py	(revision 492)
@@ -0,0 +1,145 @@
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+import mlx.config as config
+
+import os
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.bugreport
+#
+# The bug report dialog
+#
+# This module implements the bug report dialog.
+
+#------------------------------------------------------------------------------
+
+class BugReportDialog(gtk.Dialog):
+    """The dialog to report a bug."""
+    def __init__(self, gui):
+        super(BugReportDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
+                                              xstr("bugreport_title"),
+                                              gui.mainWindow,
+                                              DIALOG_MODAL)
+
+        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
+        self._sendButton = self.add_button(xstr("button_send"), RESPONSETYPE_ACCEPT)
+        self._sendButton.set_can_default(True)
+        self._gui = gui
+
+        contentArea = self.get_content_area()
+
+        contentAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                         xscale = 0.0, yscale = 0.0)
+        contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
+                                     padding_left = 8, padding_right = 8)
+
+        contentArea.pack_start(contentAlignment, False, False, 0)
+
+        contentVBox = gtk.VBox()
+        contentAlignment.add(contentVBox)
+
+        label = gtk.Label(xstr("bugreport_summary"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+
+        contentVBox.pack_start(label, False, False, 4)
+
+        self._summary = summary = gtk.Entry()
+        summary.connect("changed", self._summaryChanged)
+        summary.set_tooltip_text(xstr("bugreport_summary_tooltip"))
+        summary.set_width_chars(80)
+        label.set_mnemonic_widget(summary)
+        contentVBox.pack_start(summary, True, True, 4)
+
+        label = gtk.Label(xstr("bugreport_description"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+
+        contentVBox.pack_start(label, False, False, 4)
+
+        self._description = description = gtk.TextView()
+        description.set_tooltip_text(xstr("bugreport_description_tooltip"))
+        description.set_wrap_mode(WRAP_WORD)
+        label.set_mnemonic_widget(description)
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(description)
+        scrolledWindow.set_size_request(-1, 200)
+        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(SHADOW_IN)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 1.0, yscale = 1.0)
+        alignment.add(scrolledWindow)
+
+        contentVBox.pack_start(alignment, True, True, 4)
+
+        emailBox = gtk.HBox()
+        contentVBox.pack_start(emailBox, False, False, 4)
+
+        label = gtk.Label(xstr("bugreport_email"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+
+        emailBox.pack_start(label, False, False, 0)
+
+        alignment = gtk.Alignment()
+        emailBox.pack_start(alignment, False, False, 8)
+
+        self._email = email = gtk.Entry()
+        email.set_tooltip_text(xstr("bugreport_email_tooltip"))
+        label.set_mnemonic_widget(email)
+        emailBox.pack_start(email, True, True, 0)
+
+
+    def run(self):
+        """Run the checklist editor dialog."""
+        self.set_sensitive(True)
+        self._description.set_sensitive(True)
+        self._updateButtons()
+        self._sendButton.grab_default()
+        self.show_all()
+        response = super(BugReportDialog, self).run()
+
+        print "response", response, RESPONSETYPE_ACCEPT
+        if response==RESPONSETYPE_ACCEPT:
+            self._send()
+        else:
+            self.hide()
+
+    def _summaryChanged(self, entry):
+        """Called when the summary has changed."""
+        self._updateButtons()
+
+    def _updateButtons(self):
+        """Update the sensitivity of the buttoms."""
+        self._sendButton.set_sensitive(self._summary.get_text()!="")
+
+    def _send(self):
+        """Send the bug report."""
+        descriptionBuffer = self._description.get_buffer()
+        description = \
+          descriptionBuffer.get_text(descriptionBuffer.get_start_iter(),
+                                     descriptionBuffer.get_end_iter(),
+                                     False)
+        description = text2unicode(description)
+        self.set_sensitive(False)
+        self._gui.sendBugReport(text2unicode(self._summary.get_text()),
+                                description,
+                                text2unicode(self._email.get_text()),
+                                self._bugReportSent)
+
+    def _bugReportSent(self, returned, result):
+        """Called when the bug report was sent."""
+        self.set_sensitive(True)
+        self._description.set_sensitive(True)
+        if returned and result.success:
+            self.hide()
+            self._summary.set_text("")
+            self._description.get_buffer().set_text("")
+            self._email.set_text("")
+        else:
+            self.run()
Index: src/mlx/gui/bugreport.py
===================================================================
--- src/mlx/gui/bugreport.py	(revision 492)
+++ 	(revision )
@@ -1,145 +1,0 @@
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-import mlx.config as config
-
-import os
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.bugreport
-#
-# The bug report dialog
-#
-# This module implements the bug report dialog.
-
-#------------------------------------------------------------------------------
-
-class BugReportDialog(gtk.Dialog):
-    """The dialog to report a bug."""
-    def __init__(self, gui):
-        super(BugReportDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
-                                              xstr("bugreport_title"),
-                                              gui.mainWindow,
-                                              DIALOG_MODAL)
-
-        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
-        self._sendButton = self.add_button(xstr("button_send"), RESPONSETYPE_ACCEPT)
-        self._sendButton.set_can_default(True)
-        self._gui = gui
-
-        contentArea = self.get_content_area()
-
-        contentAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                         xscale = 0.0, yscale = 0.0)
-        contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
-                                     padding_left = 8, padding_right = 8)
-
-        contentArea.pack_start(contentAlignment, False, False, 0)
-
-        contentVBox = gtk.VBox()
-        contentAlignment.add(contentVBox)
-
-        label = gtk.Label(xstr("bugreport_summary"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-
-        contentVBox.pack_start(label, False, False, 4)
-
-        self._summary = summary = gtk.Entry()
-        summary.connect("changed", self._summaryChanged)
-        summary.set_tooltip_text(xstr("bugreport_summary_tooltip"))
-        summary.set_width_chars(80)
-        label.set_mnemonic_widget(summary)
-        contentVBox.pack_start(summary, True, True, 4)
-
-        label = gtk.Label(xstr("bugreport_description"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-
-        contentVBox.pack_start(label, False, False, 4)
-
-        self._description = description = gtk.TextView()
-        description.set_tooltip_text(xstr("bugreport_description_tooltip"))
-        description.set_wrap_mode(WRAP_WORD)
-        label.set_mnemonic_widget(description)
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(description)
-        scrolledWindow.set_size_request(-1, 200)
-        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(SHADOW_IN)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 1.0, yscale = 1.0)
-        alignment.add(scrolledWindow)
-
-        contentVBox.pack_start(alignment, True, True, 4)
-
-        emailBox = gtk.HBox()
-        contentVBox.pack_start(emailBox, False, False, 4)
-
-        label = gtk.Label(xstr("bugreport_email"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-
-        emailBox.pack_start(label, False, False, 0)
-
-        alignment = gtk.Alignment()
-        emailBox.pack_start(alignment, False, False, 8)
-
-        self._email = email = gtk.Entry()
-        email.set_tooltip_text(xstr("bugreport_email_tooltip"))
-        label.set_mnemonic_widget(email)
-        emailBox.pack_start(email, True, True, 0)
-
-
-    def run(self):
-        """Run the checklist editor dialog."""
-        self.set_sensitive(True)
-        self._description.set_sensitive(True)
-        self._updateButtons()
-        self._sendButton.grab_default()
-        self.show_all()
-        response = super(BugReportDialog, self).run()
-
-        print "response", response, RESPONSETYPE_ACCEPT
-        if response==RESPONSETYPE_ACCEPT:
-            self._send()
-        else:
-            self.hide()
-
-    def _summaryChanged(self, entry):
-        """Called when the summary has changed."""
-        self._updateButtons()
-
-    def _updateButtons(self):
-        """Update the sensitivity of the buttoms."""
-        self._sendButton.set_sensitive(self._summary.get_text()!="")
-
-    def _send(self):
-        """Send the bug report."""
-        descriptionBuffer = self._description.get_buffer()
-        description = \
-          descriptionBuffer.get_text(descriptionBuffer.get_start_iter(),
-                                     descriptionBuffer.get_end_iter(),
-                                     False)
-        description = text2unicode(description)
-        self.set_sensitive(False)
-        self._gui.sendBugReport(text2unicode(self._summary.get_text()),
-                                description,
-                                text2unicode(self._email.get_text()),
-                                self._bugReportSent)
-
-    def _bugReportSent(self, returned, result):
-        """Called when the bug report was sent."""
-        self.set_sensitive(True)
-        self._description.set_sensitive(True)
-        if returned and result.success:
-            self.hide()
-            self._summary.set_text("")
-            self._description.get_buffer().set_text("")
-            self._email.set_text("")
-        else:
-            self.run()
Index: src/mlx/gui/callouts.py
===================================================================
--- src/mlx/gui/callouts.py	(revision 300)
+++ src/mlx/gui/callouts.py	(revision 300)
@@ -0,0 +1,503 @@
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+import mlx.config as config
+
+import os
+import re
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.callouts
+#
+# Editor dialog for approach callouts.
+#
+# The dialog consists of an aircraft type selector box at the top, and a table
+# with two buttons below it. The table contains the callout files with the
+# corresponding altitudes, and is sorted according to the altitude. When a new
+# file is added, the program finds out a new altitude for it. If the file's
+# name contains numbers that are not used as altitudes yet, the most suitable
+# of those numbers will be used. Otherwise a 'usual' altitude is searched for,
+# in the direction according to the sort order, and if that fails too, the
+# altitudes are tried one-by-one. See the
+# \ref ApproachCalloutsEditor._getNewAltitude function for more details.
+
+#------------------------------------------------------------------------------
+
+class ApproachCalloutsEditor(gtk.Dialog):
+    """The dialog to edit the approach callouts."""
+    integerRE = re.compile("[0-9]+")
+
+    # A list of "usual" altitudes for callouts
+    _usualAltitudes = [10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000,
+                       1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000]
+
+    @staticmethod
+    def _getNextUsualAltitude(altitude, descending):
+        """Get the next altitude coming after the given one in the
+        given direction."""
+        if descending:
+            previous = None
+            for alt in ApproachCalloutsEditor._usualAltitudes:
+                if alt>=altitude: return previous
+                previous = alt
+        else:
+            for alt in ApproachCalloutsEditor._usualAltitudes:
+                if alt>altitude: return alt                
+                    
+        return None
+
+    def __init__(self, gui):
+        super(ApproachCalloutsEditor, self).__init__(WINDOW_TITLE_BASE + " - " +
+                                                     xstr("callouts_title"),
+                                                     gui.mainWindow,
+                                                     DIALOG_MODAL)
+
+        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
+        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
+
+        self._gui = gui
+        self._approachCallouts = {}
+        self._currentAircraftType = const.aircraftTypes[0]
+        self._fileOpenDialog = None
+
+        contentArea = self.get_content_area()
+
+        # FIXME: common code with the checklist editor
+        typeBox = gtk.HBox()
+
+        label = gtk.Label(xstr("callouts_aircraftType"))
+        label.set_use_underline(True)
+
+        typeBox.pack_start(label, False, False, 4)
+
+        self._aircraftTypeModel = gtk.ListStore(str, int)
+        for type in const.aircraftTypes:
+            name = aircraftNames[type] if type in aircraftNames \
+                   else "Aircraft type #%d" % (type,)
+            self._aircraftTypeModel.append([name, type])
+        self._aircraftType = gtk.ComboBox(model = self._aircraftTypeModel)
+        renderer = gtk.CellRendererText()
+        self._aircraftType.pack_start(renderer, True)
+        self._aircraftType.add_attribute(renderer, "text", 0)
+        self._aircraftType.set_tooltip_text(xstr("callouts_aircraftType_tooltip"))
+        self._aircraftType.set_active(0)
+        self._aircraftType.connect("changed", self._aircraftTypeChanged)
+        label.set_mnemonic_widget(self._aircraftType)
+
+        typeBox.pack_start(self._aircraftType, True, True, 4)
+
+        typeBoxAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                         xscale = 0.0, yscale = 0.0)
+        typeBoxAlignment.set_size_request(400, -1)
+        typeBoxAlignment.add(typeBox)
+
+        contentArea.pack_start(typeBoxAlignment, False, False, 12)
+        # FIXME: common code until here, but note that some texts are different
+
+        contentBox = gtk.HBox()        
+
+        controlBox = gtk.VBox()
+        controlAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                         xscale = 0.0, yscale = 0.0)
+        controlAlignment.set_padding(padding_top = 0, padding_bottom = 0,
+                                     padding_left = 32, padding_right = 32)
+        controlAlignment.add(controlBox)
+        contentBox.pack_start(controlAlignment, False, False, 0)
+
+        self._addButton = gtk.Button(xstr("callouts_add"))
+        self._addButton.set_use_underline(True)
+        self._addButton.set_tooltip_text(xstr("callouts_add_tooltip"))
+        self._addButton.connect("clicked", self._addButtonClicked)
+        addAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                     xscale = 0.0, yscale = 0.0)
+        addAlignment.set_padding(padding_top = 24, padding_bottom = 0,
+                                 padding_left = 0, padding_right = 0)
+        addAlignment.add(self._addButton)
+        controlBox.pack_start(addAlignment, False, False, 0)
+
+        self._removeButton = gtk.Button(xstr("callouts_remove"))
+        self._removeButton.set_use_underline(True)
+        self._removeButton.set_tooltip_text(xstr("callouts_remove_tooltip"))
+        self._removeButton.set_sensitive(False)
+        self._removeButton.connect("clicked", self._removeButtonClicked)
+
+        removeAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                        xscale = 0.0, yscale = 0.0)
+        removeAlignment.set_padding(padding_top = 24, padding_bottom = 0,
+                                    padding_left = 0, padding_right = 0)
+        removeAlignment.add(self._removeButton)
+        controlBox.pack_start(removeAlignment, False, False, 0)
+
+        self._fileListModel = gtk.ListStore(int, str, str)
+        self._fileListModel.set_sort_column_id(0, SORT_DESCENDING)
+
+        self._addingFile = False
+        self._fileListModel.connect("row-inserted", self._fileAdded)
+        self._lastAddedAltitude = None
+        
+        self._fileList = gtk.TreeView(model = self._fileListModel)
+
+        renderer = gtk.CellRendererSpin()
+        renderer.set_property("editable", True)
+
+        adjustment = gtk.Adjustment(0, 0, 5000, 10, 100)
+        renderer.set_property("adjustment", adjustment);
+        renderer.connect("edited", self._altitudeEdited)
+        
+        column = gtk.TreeViewColumn(xstr("callouts_header_altitude"),
+                                    renderer, text = 0)
+        self._fileList.append_column(column)
+        column.set_expand(True)
+        column.set_clickable(True)
+        column.set_reorderable(False)
+        column.set_sort_indicator(True)
+        column.set_sort_column_id(0)
+        column.set_sort_order(SORT_DESCENDING)
+        column.set_expand(False)
+
+        column = gtk.TreeViewColumn(xstr("callouts_header_path"),
+                                    gtk.CellRendererText(), text = 1)
+        self._fileList.append_column(column)
+        column.set_expand(True)
+        column.set_clickable(False)
+        column.set_reorderable(False)
+        column.set_expand(True)
+        
+        self._fileList.set_tooltip_column(2)
+        self._fileList.set_size_request(300, -1)
+        self._fileList.set_reorderable(False)
+        self._fileList.connect("button-press-event",
+                               self._fileListButtonPressed)
+        selection = self._fileList.get_selection()
+        selection.set_mode(SELECTION_MULTIPLE)
+        selection.connect("changed", self._fileListSelectionChanged)
+
+        self._buildFileListPopupMenu()
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._fileList)
+        scrolledWindow.set_size_request(300, -1)
+        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(SHADOW_IN)
+        
+        fileListAlignment = gtk.Alignment(xscale=1.0, yscale=1.0, 
+                                          xalign=0.5, yalign=0.5)
+        fileListAlignment.set_padding(padding_top = 0, padding_bottom = 16,
+                                      padding_left = 0, padding_right = 8)
+        fileListAlignment.add(scrolledWindow)
+        
+        contentBox.pack_start(fileListAlignment, False, False, 4)
+
+        contentArea.pack_start(contentBox, True, True, 4)
+
+        self.set_size_request(-1, 300)
+
+    def run(self):
+        """Run the approach callouts editor dialog."""
+        self._approachCallouts = {}
+        self._displayCurrentApproachCallouts()
+        self.show_all()
+        response = super(ApproachCalloutsEditor, self).run()
+        self.hide()
+
+        if response==RESPONSETYPE_ACCEPT:
+            self._saveApproachCallouts()
+            config = self._gui.config
+            for (aircraftType, approachCallouts) in \
+                    self._approachCallouts.iteritems():
+                config.setApproachCallouts(aircraftType, approachCallouts)
+            config.save()
+
+    def _aircraftTypeChanged(self, comboBox):
+        """Called when the aircraft's type has changed."""        
+        self._saveApproachCallouts()
+        self._displayCurrentApproachCallouts()
+        
+    def _addButtonClicked(self, button):
+        """Called when the Add button is clicked."""
+        dialog = self._getFileOpenDialog()
+        
+        dialog.show_all()
+        result = dialog.run()
+        dialog.hide()
+
+        if result==RESPONSETYPE_OK:
+            filePath = dialog.get_filename()
+            baseName = os.path.basename(filePath)
+            altitude = self._getNewAltitude(baseName)
+            self._addingFile = True
+            self._lastAddedAltitude = altitude
+            self._fileListModel.append([altitude, baseName, filePath])
+            self._addingFile = False
+
+    def _fileAdded(self, model, path, iter):
+        """Called when a file is added to the list of callouts.
+
+        Makes the treeview to edit the altitude in the given row."""
+        if self._addingFile:
+            gobject.idle_add(self._selectFile)
+            self._fileList.grab_focus()
+            self.grab_focus()
+
+    def _selectFile(self):
+        """Select the file with the last added altitude."""
+        if self._lastAddedAltitude is None: return
+
+        model = self._fileListModel
+        iter = model.get_iter_first()
+        while iter is not None:
+            if model.get_value(iter, 0)==self._lastAddedAltitude: break
+            iter = model.iter_next(iter)
+        if iter is not None:
+            self._fileList.set_cursor(model.get_path(iter),
+                                      self._fileList.get_column(0), True)
+        self._lastAddedAltitude = None
+                             
+    def _removeButtonClicked(self, button):
+        """Called when the Remove button is clicked."""
+        self._removeSelected()
+        
+    def _removeSelected(self):
+        """Remove the selected files."""
+        selection = self._fileList.get_selection()
+        (model, paths) = selection.get_selected_rows()
+
+        iters = [model.get_iter(path) for path in paths]
+
+        for i in iters:
+            if i is not None:
+                model.remove(i)
+        
+    def _fileListSelectionChanged(self, selection):
+        """Called when the selection in the file list changes."""
+        anySelected = selection.count_selected_rows()>0
+        self._removeButton.set_sensitive(anySelected)
+
+    def _getAircraftType(self):
+        """Get the currently selected aircraft type."""
+        # FIXME: the same code as in the checklist editor
+        index = self._aircraftType.get_active()
+        return self._aircraftTypeModel[index][1]        
+
+    def _altitudeEdited(self, widget, path, value):
+        """Called when an altitude is edited"""
+        newAltitude = int(value)
+
+        model = self._fileListModel
+        editedIter = model.get_iter_from_string(path)
+        editedPath = model.get_path(editedIter)
+        otherPath = self._hasAltitude(newAltitude, ignorePath = editedPath)
+        if otherPath is not None:
+            dialog = gtk.MessageDialog(parent = self,
+                                       type = MESSAGETYPE_QUESTION,
+                                       message_format =
+                                       xstr("callouts_altitude_clash"))
+            dialog.format_secondary_markup(xstr("callouts_altitude_clash_sec"))
+            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:
+                newAltitude = None
+                
+        if newAltitude is not None:
+            model[editedPath][0] = newAltitude
+            if otherPath is not None:
+                model.remove(model.get_iter(otherPath))
+
+    def _saveApproachCallouts(self):
+        """Save the currently displayed list of approach callouts for the
+        previously displayed aircraft type."""
+        mapping = {}
+        model = self._fileListModel
+        iter = model.get_iter_first()
+        while iter is not None:
+            altitude = int(model.get(iter, 0)[0])
+            path = model.get(iter, 2)[0]
+            mapping[altitude] = path
+            iter = model.iter_next(iter)
+
+        self._approachCallouts[self._currentAircraftType] = \
+            config.ApproachCallouts(mapping)
+        
+    def _displayCurrentApproachCallouts(self):
+        """Display the approach callouts for the currently selected aircraft
+        type."""
+        aircraftType = self._getAircraftType()
+        self._currentAircraftType = aircraftType
+        if aircraftType not in self._approachCallouts:
+            self._approachCallouts[aircraftType] = \
+                self._gui.config.getApproachCallouts(aircraftType).clone()
+        approachCallouts = self._approachCallouts[aircraftType]
+
+        self._fileListModel.clear()
+        for (altitude, path) in approachCallouts:
+            self._fileListModel.append([altitude, os.path.basename(path), path])        
+
+    def _getFileOpenDialog(self):
+        """Get the dialog to open a file.
+
+        If it does not exist yet, it will be created."""
+        if self._fileOpenDialog is None:
+            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                           xstr("callouts_open_title"),
+                                           action = FILE_CHOOSER_ACTION_OPEN,
+                                           buttons = (gtk.STOCK_CANCEL,
+                                                      RESPONSETYPE_CANCEL,
+                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
+                                           parent = self)
+            dialog.set_modal(True)            
+            dialog.set_do_overwrite_confirmation(True)
+      
+            # FIXME: create the filters in one location and use them
+            # from there
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("file_filter_audio"))
+            filter.add_pattern("*.wav")
+            filter.add_pattern("*.mp3")
+            dialog.add_filter(filter)
+
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("file_filter_all"))
+            filter.add_pattern("*.*")
+            dialog.add_filter(filter)
+            
+            self._fileOpenDialog = dialog
+
+        return self._fileOpenDialog
+    
+    def _getNewAltitude(self, baseName):
+        """Get a new, unique altitude for the audio file with the given
+        base name.
+
+        First the given file name is searched for suitable
+        numbers. Otherwise the smallest altitude in the model is
+        considered, and, depending on the actual ordering of the
+        table, a suitable smaller or greater value is found. It is
+        ensured that the number is unique, unless all numbers are
+        taken.
+        
+        If there is no entry in the table yet, 2500 is returned if the
+        table is sorted descending, 10 otherwise."""
+        altitude = self._getNewAltitudeFromFileName(baseName)
+        if altitude is not None: return altitude
+
+        descending = self._fileList.get_column(0).get_sort_order()==SORT_DESCENDING
+        model = self._fileListModel
+        numEntries = model.iter_n_children(None)
+        if numEntries==0:
+            return 2500 if descending else 10
+        else:
+            selection = self._fileList.get_selection()
+            (_model, paths) = selection.get_selected_rows()
+
+            if paths:
+                startIter = model.get_iter(max(paths))
+            else:
+                startIter = model.iter_nth_child(None, numEntries-1)
+
+            startValue = model.get_value(startIter, 0)
+
+            altitude = self._getNextValidUsualAltitude(startValue, descending)
+            if altitude is None:
+                altitude = self._getNextValidUsualAltitude(startValue,
+                                                           not descending)
+
+            if altitude is None:
+                for altitude in range(0 if descending else 4999,
+                                      4999 if descending else 0,
+                                      1 if descending else -1):
+                    if not self._hasAltitude(altitude): break
+                                   
+            return altitude
+
+    def _getNewAltitudeFromFileName(self, baseName):
+        """Get a new altitude value from the given file name.
+
+        The name is traversed for numbers. If a number is less than
+        5000 and there is no such altitude yet in the table, it is
+        checked if it is divisible by 100 or 1000, and if so, it gets
+        a score of 2. If it is divisible by 10, the score will be 1,
+        otherwise 0. The first highest scoring number is returned, if
+        there are any at all, otherwise None."""
+        candidateAltitude = None
+        candidateScore = None
+        
+        (baseName, _) = os.path.splitext(baseName)
+        numbers = ApproachCalloutsEditor.integerRE.findall(baseName)
+        for number in numbers:
+            value = int(number)
+            if value<5000 and not self._hasAltitude(value):
+                score = 2 if (value%100)==0 or (value%1000)==0 \
+                    else 1 if (value%10)==0 else 0
+                if candidateAltitude is None or score>candidateScore:
+                    candidateAltitude = value
+                    candidateScore = score
+
+        return candidateAltitude        
+        
+    def _hasAltitude(self, altitude, ignorePath = None):
+        """Determine if the model already contains the given altitude
+        or not.
+        
+        ignorePath is a path in the model to ignore.
+
+        Returns the path of the element found, if any, or None, if the
+        altitude is not found."""        
+        model = self._fileListModel
+        iter = model.get_iter_first()
+        while iter is not None:
+            path = model.get_path(iter)
+            if path!=ignorePath and altitude==model[path][0]:
+                return path
+            iter = model.iter_next(iter)
+
+        return None
+
+    def _getNextValidUsualAltitude(self, startValue, descending):
+        """Get the next valid usual altitude."""
+        value = startValue
+        while value is not None and self._hasAltitude(value):
+            value = \
+                ApproachCalloutsEditor._getNextUsualAltitude(value, 
+                                                             descending)        
+
+        return value
+        
+    def _fileListButtonPressed(self, widget, event):
+        """Called when a mouse button is pressed on the file list."""
+        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
+            return
+
+        menu = self._fileListPopupMenu
+        if pygobject:
+            menu.popup(None, None, None, None, event.button, event.time)
+        else:
+            menu.popup(None, None, None, event.button, event.time)
+
+    def _buildFileListPopupMenu(self):
+        """Build the file list popup menu."""
+        menu = gtk.Menu()
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("callouts_remove"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupRemove)
+        menuItem.show()
+        self._popupRemoveItem = menuItem
+
+        menu.append(menuItem)
+
+        self._fileListPopupMenu = menu
+
+    def _popupRemove(self, menuItem):
+        """Remove the currently selected menu items."""
+        self._removeSelected()
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/callouts.py
===================================================================
--- src/mlx/gui/callouts.py	(revision 300)
+++ 	(revision )
@@ -1,503 +1,0 @@
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-import mlx.config as config
-
-import os
-import re
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.callouts
-#
-# Editor dialog for approach callouts.
-#
-# The dialog consists of an aircraft type selector box at the top, and a table
-# with two buttons below it. The table contains the callout files with the
-# corresponding altitudes, and is sorted according to the altitude. When a new
-# file is added, the program finds out a new altitude for it. If the file's
-# name contains numbers that are not used as altitudes yet, the most suitable
-# of those numbers will be used. Otherwise a 'usual' altitude is searched for,
-# in the direction according to the sort order, and if that fails too, the
-# altitudes are tried one-by-one. See the
-# \ref ApproachCalloutsEditor._getNewAltitude function for more details.
-
-#------------------------------------------------------------------------------
-
-class ApproachCalloutsEditor(gtk.Dialog):
-    """The dialog to edit the approach callouts."""
-    integerRE = re.compile("[0-9]+")
-
-    # A list of "usual" altitudes for callouts
-    _usualAltitudes = [10, 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000,
-                       1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000]
-
-    @staticmethod
-    def _getNextUsualAltitude(altitude, descending):
-        """Get the next altitude coming after the given one in the
-        given direction."""
-        if descending:
-            previous = None
-            for alt in ApproachCalloutsEditor._usualAltitudes:
-                if alt>=altitude: return previous
-                previous = alt
-        else:
-            for alt in ApproachCalloutsEditor._usualAltitudes:
-                if alt>altitude: return alt                
-                    
-        return None
-
-    def __init__(self, gui):
-        super(ApproachCalloutsEditor, self).__init__(WINDOW_TITLE_BASE + " - " +
-                                                     xstr("callouts_title"),
-                                                     gui.mainWindow,
-                                                     DIALOG_MODAL)
-
-        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
-        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
-
-        self._gui = gui
-        self._approachCallouts = {}
-        self._currentAircraftType = const.aircraftTypes[0]
-        self._fileOpenDialog = None
-
-        contentArea = self.get_content_area()
-
-        # FIXME: common code with the checklist editor
-        typeBox = gtk.HBox()
-
-        label = gtk.Label(xstr("callouts_aircraftType"))
-        label.set_use_underline(True)
-
-        typeBox.pack_start(label, False, False, 4)
-
-        self._aircraftTypeModel = gtk.ListStore(str, int)
-        for type in const.aircraftTypes:
-            name = aircraftNames[type] if type in aircraftNames \
-                   else "Aircraft type #%d" % (type,)
-            self._aircraftTypeModel.append([name, type])
-        self._aircraftType = gtk.ComboBox(model = self._aircraftTypeModel)
-        renderer = gtk.CellRendererText()
-        self._aircraftType.pack_start(renderer, True)
-        self._aircraftType.add_attribute(renderer, "text", 0)
-        self._aircraftType.set_tooltip_text(xstr("callouts_aircraftType_tooltip"))
-        self._aircraftType.set_active(0)
-        self._aircraftType.connect("changed", self._aircraftTypeChanged)
-        label.set_mnemonic_widget(self._aircraftType)
-
-        typeBox.pack_start(self._aircraftType, True, True, 4)
-
-        typeBoxAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                         xscale = 0.0, yscale = 0.0)
-        typeBoxAlignment.set_size_request(400, -1)
-        typeBoxAlignment.add(typeBox)
-
-        contentArea.pack_start(typeBoxAlignment, False, False, 12)
-        # FIXME: common code until here, but note that some texts are different
-
-        contentBox = gtk.HBox()        
-
-        controlBox = gtk.VBox()
-        controlAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                         xscale = 0.0, yscale = 0.0)
-        controlAlignment.set_padding(padding_top = 0, padding_bottom = 0,
-                                     padding_left = 32, padding_right = 32)
-        controlAlignment.add(controlBox)
-        contentBox.pack_start(controlAlignment, False, False, 0)
-
-        self._addButton = gtk.Button(xstr("callouts_add"))
-        self._addButton.set_use_underline(True)
-        self._addButton.set_tooltip_text(xstr("callouts_add_tooltip"))
-        self._addButton.connect("clicked", self._addButtonClicked)
-        addAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                     xscale = 0.0, yscale = 0.0)
-        addAlignment.set_padding(padding_top = 24, padding_bottom = 0,
-                                 padding_left = 0, padding_right = 0)
-        addAlignment.add(self._addButton)
-        controlBox.pack_start(addAlignment, False, False, 0)
-
-        self._removeButton = gtk.Button(xstr("callouts_remove"))
-        self._removeButton.set_use_underline(True)
-        self._removeButton.set_tooltip_text(xstr("callouts_remove_tooltip"))
-        self._removeButton.set_sensitive(False)
-        self._removeButton.connect("clicked", self._removeButtonClicked)
-
-        removeAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                        xscale = 0.0, yscale = 0.0)
-        removeAlignment.set_padding(padding_top = 24, padding_bottom = 0,
-                                    padding_left = 0, padding_right = 0)
-        removeAlignment.add(self._removeButton)
-        controlBox.pack_start(removeAlignment, False, False, 0)
-
-        self._fileListModel = gtk.ListStore(int, str, str)
-        self._fileListModel.set_sort_column_id(0, SORT_DESCENDING)
-
-        self._addingFile = False
-        self._fileListModel.connect("row-inserted", self._fileAdded)
-        self._lastAddedAltitude = None
-        
-        self._fileList = gtk.TreeView(model = self._fileListModel)
-
-        renderer = gtk.CellRendererSpin()
-        renderer.set_property("editable", True)
-
-        adjustment = gtk.Adjustment(0, 0, 5000, 10, 100)
-        renderer.set_property("adjustment", adjustment);
-        renderer.connect("edited", self._altitudeEdited)
-        
-        column = gtk.TreeViewColumn(xstr("callouts_header_altitude"),
-                                    renderer, text = 0)
-        self._fileList.append_column(column)
-        column.set_expand(True)
-        column.set_clickable(True)
-        column.set_reorderable(False)
-        column.set_sort_indicator(True)
-        column.set_sort_column_id(0)
-        column.set_sort_order(SORT_DESCENDING)
-        column.set_expand(False)
-
-        column = gtk.TreeViewColumn(xstr("callouts_header_path"),
-                                    gtk.CellRendererText(), text = 1)
-        self._fileList.append_column(column)
-        column.set_expand(True)
-        column.set_clickable(False)
-        column.set_reorderable(False)
-        column.set_expand(True)
-        
-        self._fileList.set_tooltip_column(2)
-        self._fileList.set_size_request(300, -1)
-        self._fileList.set_reorderable(False)
-        self._fileList.connect("button-press-event",
-                               self._fileListButtonPressed)
-        selection = self._fileList.get_selection()
-        selection.set_mode(SELECTION_MULTIPLE)
-        selection.connect("changed", self._fileListSelectionChanged)
-
-        self._buildFileListPopupMenu()
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._fileList)
-        scrolledWindow.set_size_request(300, -1)
-        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(SHADOW_IN)
-        
-        fileListAlignment = gtk.Alignment(xscale=1.0, yscale=1.0, 
-                                          xalign=0.5, yalign=0.5)
-        fileListAlignment.set_padding(padding_top = 0, padding_bottom = 16,
-                                      padding_left = 0, padding_right = 8)
-        fileListAlignment.add(scrolledWindow)
-        
-        contentBox.pack_start(fileListAlignment, False, False, 4)
-
-        contentArea.pack_start(contentBox, True, True, 4)
-
-        self.set_size_request(-1, 300)
-
-    def run(self):
-        """Run the approach callouts editor dialog."""
-        self._approachCallouts = {}
-        self._displayCurrentApproachCallouts()
-        self.show_all()
-        response = super(ApproachCalloutsEditor, self).run()
-        self.hide()
-
-        if response==RESPONSETYPE_ACCEPT:
-            self._saveApproachCallouts()
-            config = self._gui.config
-            for (aircraftType, approachCallouts) in \
-                    self._approachCallouts.iteritems():
-                config.setApproachCallouts(aircraftType, approachCallouts)
-            config.save()
-
-    def _aircraftTypeChanged(self, comboBox):
-        """Called when the aircraft's type has changed."""        
-        self._saveApproachCallouts()
-        self._displayCurrentApproachCallouts()
-        
-    def _addButtonClicked(self, button):
-        """Called when the Add button is clicked."""
-        dialog = self._getFileOpenDialog()
-        
-        dialog.show_all()
-        result = dialog.run()
-        dialog.hide()
-
-        if result==RESPONSETYPE_OK:
-            filePath = dialog.get_filename()
-            baseName = os.path.basename(filePath)
-            altitude = self._getNewAltitude(baseName)
-            self._addingFile = True
-            self._lastAddedAltitude = altitude
-            self._fileListModel.append([altitude, baseName, filePath])
-            self._addingFile = False
-
-    def _fileAdded(self, model, path, iter):
-        """Called when a file is added to the list of callouts.
-
-        Makes the treeview to edit the altitude in the given row."""
-        if self._addingFile:
-            gobject.idle_add(self._selectFile)
-            self._fileList.grab_focus()
-            self.grab_focus()
-
-    def _selectFile(self):
-        """Select the file with the last added altitude."""
-        if self._lastAddedAltitude is None: return
-
-        model = self._fileListModel
-        iter = model.get_iter_first()
-        while iter is not None:
-            if model.get_value(iter, 0)==self._lastAddedAltitude: break
-            iter = model.iter_next(iter)
-        if iter is not None:
-            self._fileList.set_cursor(model.get_path(iter),
-                                      self._fileList.get_column(0), True)
-        self._lastAddedAltitude = None
-                             
-    def _removeButtonClicked(self, button):
-        """Called when the Remove button is clicked."""
-        self._removeSelected()
-        
-    def _removeSelected(self):
-        """Remove the selected files."""
-        selection = self._fileList.get_selection()
-        (model, paths) = selection.get_selected_rows()
-
-        iters = [model.get_iter(path) for path in paths]
-
-        for i in iters:
-            if i is not None:
-                model.remove(i)
-        
-    def _fileListSelectionChanged(self, selection):
-        """Called when the selection in the file list changes."""
-        anySelected = selection.count_selected_rows()>0
-        self._removeButton.set_sensitive(anySelected)
-
-    def _getAircraftType(self):
-        """Get the currently selected aircraft type."""
-        # FIXME: the same code as in the checklist editor
-        index = self._aircraftType.get_active()
-        return self._aircraftTypeModel[index][1]        
-
-    def _altitudeEdited(self, widget, path, value):
-        """Called when an altitude is edited"""
-        newAltitude = int(value)
-
-        model = self._fileListModel
-        editedIter = model.get_iter_from_string(path)
-        editedPath = model.get_path(editedIter)
-        otherPath = self._hasAltitude(newAltitude, ignorePath = editedPath)
-        if otherPath is not None:
-            dialog = gtk.MessageDialog(parent = self,
-                                       type = MESSAGETYPE_QUESTION,
-                                       message_format =
-                                       xstr("callouts_altitude_clash"))
-            dialog.format_secondary_markup(xstr("callouts_altitude_clash_sec"))
-            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:
-                newAltitude = None
-                
-        if newAltitude is not None:
-            model[editedPath][0] = newAltitude
-            if otherPath is not None:
-                model.remove(model.get_iter(otherPath))
-
-    def _saveApproachCallouts(self):
-        """Save the currently displayed list of approach callouts for the
-        previously displayed aircraft type."""
-        mapping = {}
-        model = self._fileListModel
-        iter = model.get_iter_first()
-        while iter is not None:
-            altitude = int(model.get(iter, 0)[0])
-            path = model.get(iter, 2)[0]
-            mapping[altitude] = path
-            iter = model.iter_next(iter)
-
-        self._approachCallouts[self._currentAircraftType] = \
-            config.ApproachCallouts(mapping)
-        
-    def _displayCurrentApproachCallouts(self):
-        """Display the approach callouts for the currently selected aircraft
-        type."""
-        aircraftType = self._getAircraftType()
-        self._currentAircraftType = aircraftType
-        if aircraftType not in self._approachCallouts:
-            self._approachCallouts[aircraftType] = \
-                self._gui.config.getApproachCallouts(aircraftType).clone()
-        approachCallouts = self._approachCallouts[aircraftType]
-
-        self._fileListModel.clear()
-        for (altitude, path) in approachCallouts:
-            self._fileListModel.append([altitude, os.path.basename(path), path])        
-
-    def _getFileOpenDialog(self):
-        """Get the dialog to open a file.
-
-        If it does not exist yet, it will be created."""
-        if self._fileOpenDialog is None:
-            dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                           xstr("callouts_open_title"),
-                                           action = FILE_CHOOSER_ACTION_OPEN,
-                                           buttons = (gtk.STOCK_CANCEL,
-                                                      RESPONSETYPE_CANCEL,
-                                                      gtk.STOCK_OK, RESPONSETYPE_OK),
-                                           parent = self)
-            dialog.set_modal(True)            
-            dialog.set_do_overwrite_confirmation(True)
-      
-            # FIXME: create the filters in one location and use them
-            # from there
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("file_filter_audio"))
-            filter.add_pattern("*.wav")
-            filter.add_pattern("*.mp3")
-            dialog.add_filter(filter)
-
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("file_filter_all"))
-            filter.add_pattern("*.*")
-            dialog.add_filter(filter)
-            
-            self._fileOpenDialog = dialog
-
-        return self._fileOpenDialog
-    
-    def _getNewAltitude(self, baseName):
-        """Get a new, unique altitude for the audio file with the given
-        base name.
-
-        First the given file name is searched for suitable
-        numbers. Otherwise the smallest altitude in the model is
-        considered, and, depending on the actual ordering of the
-        table, a suitable smaller or greater value is found. It is
-        ensured that the number is unique, unless all numbers are
-        taken.
-        
-        If there is no entry in the table yet, 2500 is returned if the
-        table is sorted descending, 10 otherwise."""
-        altitude = self._getNewAltitudeFromFileName(baseName)
-        if altitude is not None: return altitude
-
-        descending = self._fileList.get_column(0).get_sort_order()==SORT_DESCENDING
-        model = self._fileListModel
-        numEntries = model.iter_n_children(None)
-        if numEntries==0:
-            return 2500 if descending else 10
-        else:
-            selection = self._fileList.get_selection()
-            (_model, paths) = selection.get_selected_rows()
-
-            if paths:
-                startIter = model.get_iter(max(paths))
-            else:
-                startIter = model.iter_nth_child(None, numEntries-1)
-
-            startValue = model.get_value(startIter, 0)
-
-            altitude = self._getNextValidUsualAltitude(startValue, descending)
-            if altitude is None:
-                altitude = self._getNextValidUsualAltitude(startValue,
-                                                           not descending)
-
-            if altitude is None:
-                for altitude in range(0 if descending else 4999,
-                                      4999 if descending else 0,
-                                      1 if descending else -1):
-                    if not self._hasAltitude(altitude): break
-                                   
-            return altitude
-
-    def _getNewAltitudeFromFileName(self, baseName):
-        """Get a new altitude value from the given file name.
-
-        The name is traversed for numbers. If a number is less than
-        5000 and there is no such altitude yet in the table, it is
-        checked if it is divisible by 100 or 1000, and if so, it gets
-        a score of 2. If it is divisible by 10, the score will be 1,
-        otherwise 0. The first highest scoring number is returned, if
-        there are any at all, otherwise None."""
-        candidateAltitude = None
-        candidateScore = None
-        
-        (baseName, _) = os.path.splitext(baseName)
-        numbers = ApproachCalloutsEditor.integerRE.findall(baseName)
-        for number in numbers:
-            value = int(number)
-            if value<5000 and not self._hasAltitude(value):
-                score = 2 if (value%100)==0 or (value%1000)==0 \
-                    else 1 if (value%10)==0 else 0
-                if candidateAltitude is None or score>candidateScore:
-                    candidateAltitude = value
-                    candidateScore = score
-
-        return candidateAltitude        
-        
-    def _hasAltitude(self, altitude, ignorePath = None):
-        """Determine if the model already contains the given altitude
-        or not.
-        
-        ignorePath is a path in the model to ignore.
-
-        Returns the path of the element found, if any, or None, if the
-        altitude is not found."""        
-        model = self._fileListModel
-        iter = model.get_iter_first()
-        while iter is not None:
-            path = model.get_path(iter)
-            if path!=ignorePath and altitude==model[path][0]:
-                return path
-            iter = model.iter_next(iter)
-
-        return None
-
-    def _getNextValidUsualAltitude(self, startValue, descending):
-        """Get the next valid usual altitude."""
-        value = startValue
-        while value is not None and self._hasAltitude(value):
-            value = \
-                ApproachCalloutsEditor._getNextUsualAltitude(value, 
-                                                             descending)        
-
-        return value
-        
-    def _fileListButtonPressed(self, widget, event):
-        """Called when a mouse button is pressed on the file list."""
-        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
-            return
-
-        menu = self._fileListPopupMenu
-        if pygobject:
-            menu.popup(None, None, None, None, event.button, event.time)
-        else:
-            menu.popup(None, None, None, event.button, event.time)
-
-    def _buildFileListPopupMenu(self):
-        """Build the file list popup menu."""
-        menu = gtk.Menu()
-
-        menuItem = gtk.MenuItem()
-        menuItem.set_label(xstr("callouts_remove"))
-        menuItem.set_use_underline(True)
-        menuItem.connect("activate", self._popupRemove)
-        menuItem.show()
-        self._popupRemoveItem = menuItem
-
-        menu.append(menuItem)
-
-        self._fileListPopupMenu = menu
-
-    def _popupRemove(self, menuItem):
-        """Remove the currently selected menu items."""
-        self._removeSelected()
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/cef.py
===================================================================
--- src/mlx/gui/cef.py	(revision 685)
+++ src/mlx/gui/cef.py	(revision 685)
@@ -0,0 +1,361 @@
+from common import *
+
+from mava_simbrief import MavaSimbriefIntegrator
+
+from mlx.util import secondaryInstallation
+
+from cefpython3 import cefpython
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+
+import platform
+import json
+import time
+import os
+import re
+import threading
+import tempfile
+import traceback
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.cef
+#
+# Some helper stuff related to the Chrome Embedded Framework
+
+#------------------------------------------------------------------------------
+
+# Indicate if we should quit
+_toQuit = False
+
+# The Selenium thread
+_seleniumHandler = None
+
+#------------------------------------------------------------------------------
+
+def getArgsFilePath():
+    """Get the path of the argument file."""
+    if os.name=="nt":
+        return os.path.join(tempfile.gettempdir(),
+                            "mlxcef.args" +
+                            (".secondary" if secondaryInstallation else ""))
+    else:
+        import pwd
+        return os.path.join(tempfile.gettempdir(),
+                            "mlxcef." + pwd.getpwuid(os.getuid())[0] + ".args" +
+                            (".secondary" if secondaryInstallation else ""))
+
+#------------------------------------------------------------------------------
+
+class ArgsFileWaiter(threading.Thread):
+    """A thread to wait for the appearance of the arguments file."""
+    def __init__(self, initializedCallback):
+        """Construct the thread."""
+        threading.Thread.__init__(self)
+        self.daemon = True
+
+        self._initializedCallback = initializedCallback
+
+    def run(self):
+        """Repeatedly check for the existence of the arguments file.
+
+        If it is found, read it, extract the arguments and insert a job into
+        the GUI loop to perform the actual initialization of CEF."""
+        argsFilePath = getArgsFilePath()
+        print "Waiting for the arguments file '%s' to appear" % (argsFilePath,)
+
+        while not os.path.exists(argsFilePath):
+            time.sleep(0.1)
+
+        print "Got arguments, reading them."""
+
+        with open(argsFilePath, "rt") as f:
+            args = f.read().split()
+
+        gobject.idle_add(_initializeCEF, args, self._initializedCallback)
+
+#------------------------------------------------------------------------------
+
+class SeleniumHandler(threading.Thread):
+    """Thread to handle Selenium operations."""
+    def __init__(self, programDirectory):
+        """Construct the thread."""
+        threading.Thread.__init__(self)
+        self.daemon = False
+
+        self._programDirectory = programDirectory
+
+        self._commandsCondition = threading.Condition()
+        self._commands = []
+
+        self._driver = None
+
+        self._toQuit = False
+
+    @property
+    def programDirectory(self):
+        """Get the program directory."""
+        return self._programDirectory
+
+    def run(self):
+        """Create the Selenium driver and the perform any operations
+        requested."""
+        scriptName = "mlx_cef_caller"
+        if secondaryInstallation:
+            scriptName += "_secondary"
+        scriptName += ".bat" if os.name=="nt" else ".sh"
+
+        scriptPath = os.path.join(self._programDirectory, scriptName)
+        print "Creating the Selenium driver to call script", scriptPath
+
+        options = Options()
+        options.binary_location = scriptPath
+        driver = self._driver = webdriver.Chrome(chrome_options = options)
+        # try:
+        # except:
+        #     traceback.print_exc()
+
+        print "Created Selenium driver."
+        while not self._toQuit:
+            with self._commandsCondition:
+                while not self._commands:
+                    self._commandsCondition.wait()
+
+                command = self._commands[0]
+                del self._commands[0]
+
+            command()
+
+        driver.quit()
+
+    def callSimBrief(self, plan, getCredentials, updateProgress,
+                     htmlFilePath):
+        """Call SimBrief with the given plan."""
+        self._enqueue(lambda:
+                      self._callSimBrief(plan, self._driver,
+                                         getCredentials, updateProgress,
+                                         htmlFilePath))
+
+    def quit(self):
+        """Instruct the thread to quit and then join it."""
+        self._enqueue(self._quit)
+        self.join()
+
+    def _enqueue(self, command):
+        """Enqueue the given command.
+
+        command should be a function to be executed in the thread."""
+        with self._commandsCondition:
+            self._commands.append(command)
+            self._commandsCondition.notify()
+
+    def _callSimBrief(self, plan, driver,
+                      getCredentials, updateProgress, htmlFilePath):
+        """Perform the SimBrief call."""
+        integrator = MavaSimbriefIntegrator(plan = plan, driver = driver)
+        link = integrator.get_xml_link(getCredentials, updateProgress,
+                                       local_xml_debug = False,
+                                       local_html_debug = False)
+
+        updateProgress("Retrieving briefing...", False)
+
+        flight_info = integrator.get_results(link,
+                                             html_file_path = htmlFilePath)
+
+        updateProgress("Done", True)
+
+    def _quit(self):
+        """Set the _toQuit member variable to indicate that the thread should
+        quit."""
+        self._toQuit = True
+
+#------------------------------------------------------------------------------
+
+def initialize(programDirectory, initializedCallback):
+    """Initialize the Chrome Embedded Framework."""
+    global _toQuit, _seleniumHandler
+    _toQuit = False
+
+    gobject.threads_init()
+
+    argsFilePath = getArgsFilePath()
+    try:
+        os.unlink(argsFilePath)
+    except:
+        pass
+
+    _seleniumHandler = SeleniumHandler(programDirectory)
+    _seleniumHandler.start()
+
+    ArgsFileWaiter(initializedCallback).start()
+
+#------------------------------------------------------------------------------
+
+def _initializeCEF(args, initializedCallback):
+    """Perform the actual initialization of CEF using the given arguments."""
+    print "Initializing CEF with args:", args
+
+    settings = {
+        "debug": True, # cefpython debug messages in console and in log_file
+        "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
+        "log_file": "", # Set to "" to disable
+        "release_dcheck_enabled": True, # Enable only when debugging
+        # This directories must be set on Linux
+        "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
+        "resources_dir_path": cefpython.GetModuleDirectory(),
+        "browser_subprocess_path": "%s/%s" % \
+            (cefpython.GetModuleDirectory(), "subprocess"),
+    }
+
+    switches={}
+    for arg in args:
+        if arg.startswith("--"):
+            if arg != "--enable-logging":
+                assignIndex = arg.find("=")
+                if assignIndex<0:
+                    switches[arg[2:]] = ""
+                else:
+                    switches[arg[2:assignIndex]] = arg[assignIndex+1:]
+        else:
+            print "Unhandled switch", arg
+
+    cefpython.Initialize(settings, switches)
+
+    gobject.timeout_add(10, _handleTimeout)
+
+    print "Initialized, executing callback..."
+    initializedCallback()
+
+#------------------------------------------------------------------------------
+
+def getContainer():
+    """Get a container object suitable for running a browser instance
+    within."""
+    if os.name=="nt":
+        container = gtk.DrawingArea()
+        container.set_property("can-focus", True)
+        container.connect("size-allocate", _handleSizeAllocate)
+    else:
+        container = gtk.VBox(True, 0)
+
+    container.show()
+
+    return container
+
+#------------------------------------------------------------------------------
+
+def startInContainer(container, url, browserSettings = {}):
+    """Start a browser instance in the given container with the given URL."""
+    if os.name=="nt":
+        windowID = container.get_window().handle
+    else:
+        m = re.search("GtkVBox at 0x(\w+)", str(container))
+        hexID = m.group(1)
+        windowID = int(hexID, 16)
+
+    windowInfo = cefpython.WindowInfo()
+    windowInfo.SetAsChild(windowID)
+
+    return cefpython.CreateBrowserSync(windowInfo,
+                                       browserSettings = browserSettings,
+                                       navigateUrl = url)
+
+#------------------------------------------------------------------------------
+
+class OffscreenRenderHandler(object):
+    def GetRootScreenRect(self, browser, rect):
+        #print "GetRootScreenRect"
+        rect += [0, 0, 800, 600]
+        return True
+
+    def GetViewRect(self, browser, rect):
+        #print "GetViewRect"
+        rect += [0, 0, 800, 600]
+        return True
+
+    def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
+        #print "GetScreenPoint", viewX, viewY
+        rect += [viewX, viewY]
+        return True
+
+    def GetScreenInfo(self, browser, screenInfo):
+        #print "GetScreenInfo"
+        pass
+
+    def OnPopupShow(self, browser, show):
+        #print "OnPopupShow", show
+        pass
+
+    def OnPopupSize(self, browser, rect):
+        #print "OnPopupSize", rect
+        pass
+
+    def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
+        #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
+        pass
+
+    def OnCursorChange(self, browser, cursor):
+        #print "OnCursorChange", cursor
+        pass
+
+    def OnScrollOffsetChanged(self, browser):
+        #print "OnScrollOffsetChange"
+        pass
+
+    def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
+                      popupFeatures, windowInfo, client, browserSettings,
+                      noJavascriptAccess):
+        wInfo = cefpython.WindowInfo()
+        wInfo.SetAsOffscreen(int(0))
+
+        windowInfo.append(wInfo)
+
+        return False
+
+#------------------------------------------------------------------------------
+
+def initializeSimBrief():
+    """Initialize the (hidden) browser window for SimBrief."""
+    windowInfo = cefpython.WindowInfo()
+    windowInfo.SetAsOffscreen(int(0))
+
+    url = "file://" + os.path.join(_seleniumHandler.programDirectory,
+                                   "simbrief.html")
+    simBriefBrowser = cefpython.CreateBrowserSync(windowInfo,
+                                                  browserSettings = {},
+                                                  navigateUrl = url)
+    simBriefBrowser.SetClientHandler(OffscreenRenderHandler())
+
+    simBriefBrowser.SetFocus(True)
+
+#------------------------------------------------------------------------------
+
+def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
+    """Call SimBrief with the given plan."""
+    _seleniumHandler.callSimBrief(plan, getCredentials,
+                                  updateProgress, htmlFilePath)
+
+#------------------------------------------------------------------------------
+
+def finalize():
+    """Finalize the Chrome Embedded Framework."""
+    global _toQuit, _seleniumHandler
+    toQuit = True
+    _seleniumHandler.quit()
+    cefpython.Shutdown()
+
+#------------------------------------------------------------------------------
+
+def _handleTimeout():
+    """Handle the timeout by running the CEF message loop."""
+    if _toQuit:
+        return False
+    else:
+        cefpython.MessageLoopWork()
+        return True
+
+#------------------------------------------------------------------------------
+
+def _handleSizeAllocate(widget, sizeAlloc):
+    """Handle the size-allocate event."""
+    cefpython.WindowUtils.OnSize(widget.get_window().handle, 0, 0, 0)
Index: src/mlx/gui/cef.py
===================================================================
--- src/mlx/gui/cef.py	(revision 685)
+++ 	(revision )
@@ -1,361 +1,0 @@
-from common import *
-
-from mava_simbrief import MavaSimbriefIntegrator
-
-from mlx.util import secondaryInstallation
-
-from cefpython3 import cefpython
-from selenium import webdriver
-from selenium.webdriver.chrome.options import Options
-
-import platform
-import json
-import time
-import os
-import re
-import threading
-import tempfile
-import traceback
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.cef
-#
-# Some helper stuff related to the Chrome Embedded Framework
-
-#------------------------------------------------------------------------------
-
-# Indicate if we should quit
-_toQuit = False
-
-# The Selenium thread
-_seleniumHandler = None
-
-#------------------------------------------------------------------------------
-
-def getArgsFilePath():
-    """Get the path of the argument file."""
-    if os.name=="nt":
-        return os.path.join(tempfile.gettempdir(),
-                            "mlxcef.args" +
-                            (".secondary" if secondaryInstallation else ""))
-    else:
-        import pwd
-        return os.path.join(tempfile.gettempdir(),
-                            "mlxcef." + pwd.getpwuid(os.getuid())[0] + ".args" +
-                            (".secondary" if secondaryInstallation else ""))
-
-#------------------------------------------------------------------------------
-
-class ArgsFileWaiter(threading.Thread):
-    """A thread to wait for the appearance of the arguments file."""
-    def __init__(self, initializedCallback):
-        """Construct the thread."""
-        threading.Thread.__init__(self)
-        self.daemon = True
-
-        self._initializedCallback = initializedCallback
-
-    def run(self):
-        """Repeatedly check for the existence of the arguments file.
-
-        If it is found, read it, extract the arguments and insert a job into
-        the GUI loop to perform the actual initialization of CEF."""
-        argsFilePath = getArgsFilePath()
-        print "Waiting for the arguments file '%s' to appear" % (argsFilePath,)
-
-        while not os.path.exists(argsFilePath):
-            time.sleep(0.1)
-
-        print "Got arguments, reading them."""
-
-        with open(argsFilePath, "rt") as f:
-            args = f.read().split()
-
-        gobject.idle_add(_initializeCEF, args, self._initializedCallback)
-
-#------------------------------------------------------------------------------
-
-class SeleniumHandler(threading.Thread):
-    """Thread to handle Selenium operations."""
-    def __init__(self, programDirectory):
-        """Construct the thread."""
-        threading.Thread.__init__(self)
-        self.daemon = False
-
-        self._programDirectory = programDirectory
-
-        self._commandsCondition = threading.Condition()
-        self._commands = []
-
-        self._driver = None
-
-        self._toQuit = False
-
-    @property
-    def programDirectory(self):
-        """Get the program directory."""
-        return self._programDirectory
-
-    def run(self):
-        """Create the Selenium driver and the perform any operations
-        requested."""
-        scriptName = "mlx_cef_caller"
-        if secondaryInstallation:
-            scriptName += "_secondary"
-        scriptName += ".bat" if os.name=="nt" else ".sh"
-
-        scriptPath = os.path.join(self._programDirectory, scriptName)
-        print "Creating the Selenium driver to call script", scriptPath
-
-        options = Options()
-        options.binary_location = scriptPath
-        driver = self._driver = webdriver.Chrome(chrome_options = options)
-        # try:
-        # except:
-        #     traceback.print_exc()
-
-        print "Created Selenium driver."
-        while not self._toQuit:
-            with self._commandsCondition:
-                while not self._commands:
-                    self._commandsCondition.wait()
-
-                command = self._commands[0]
-                del self._commands[0]
-
-            command()
-
-        driver.quit()
-
-    def callSimBrief(self, plan, getCredentials, updateProgress,
-                     htmlFilePath):
-        """Call SimBrief with the given plan."""
-        self._enqueue(lambda:
-                      self._callSimBrief(plan, self._driver,
-                                         getCredentials, updateProgress,
-                                         htmlFilePath))
-
-    def quit(self):
-        """Instruct the thread to quit and then join it."""
-        self._enqueue(self._quit)
-        self.join()
-
-    def _enqueue(self, command):
-        """Enqueue the given command.
-
-        command should be a function to be executed in the thread."""
-        with self._commandsCondition:
-            self._commands.append(command)
-            self._commandsCondition.notify()
-
-    def _callSimBrief(self, plan, driver,
-                      getCredentials, updateProgress, htmlFilePath):
-        """Perform the SimBrief call."""
-        integrator = MavaSimbriefIntegrator(plan = plan, driver = driver)
-        link = integrator.get_xml_link(getCredentials, updateProgress,
-                                       local_xml_debug = False,
-                                       local_html_debug = False)
-
-        updateProgress("Retrieving briefing...", False)
-
-        flight_info = integrator.get_results(link,
-                                             html_file_path = htmlFilePath)
-
-        updateProgress("Done", True)
-
-    def _quit(self):
-        """Set the _toQuit member variable to indicate that the thread should
-        quit."""
-        self._toQuit = True
-
-#------------------------------------------------------------------------------
-
-def initialize(programDirectory, initializedCallback):
-    """Initialize the Chrome Embedded Framework."""
-    global _toQuit, _seleniumHandler
-    _toQuit = False
-
-    gobject.threads_init()
-
-    argsFilePath = getArgsFilePath()
-    try:
-        os.unlink(argsFilePath)
-    except:
-        pass
-
-    _seleniumHandler = SeleniumHandler(programDirectory)
-    _seleniumHandler.start()
-
-    ArgsFileWaiter(initializedCallback).start()
-
-#------------------------------------------------------------------------------
-
-def _initializeCEF(args, initializedCallback):
-    """Perform the actual initialization of CEF using the given arguments."""
-    print "Initializing CEF with args:", args
-
-    settings = {
-        "debug": True, # cefpython debug messages in console and in log_file
-        "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
-        "log_file": "", # Set to "" to disable
-        "release_dcheck_enabled": True, # Enable only when debugging
-        # This directories must be set on Linux
-        "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
-        "resources_dir_path": cefpython.GetModuleDirectory(),
-        "browser_subprocess_path": "%s/%s" % \
-            (cefpython.GetModuleDirectory(), "subprocess"),
-    }
-
-    switches={}
-    for arg in args:
-        if arg.startswith("--"):
-            if arg != "--enable-logging":
-                assignIndex = arg.find("=")
-                if assignIndex<0:
-                    switches[arg[2:]] = ""
-                else:
-                    switches[arg[2:assignIndex]] = arg[assignIndex+1:]
-        else:
-            print "Unhandled switch", arg
-
-    cefpython.Initialize(settings, switches)
-
-    gobject.timeout_add(10, _handleTimeout)
-
-    print "Initialized, executing callback..."
-    initializedCallback()
-
-#------------------------------------------------------------------------------
-
-def getContainer():
-    """Get a container object suitable for running a browser instance
-    within."""
-    if os.name=="nt":
-        container = gtk.DrawingArea()
-        container.set_property("can-focus", True)
-        container.connect("size-allocate", _handleSizeAllocate)
-    else:
-        container = gtk.VBox(True, 0)
-
-    container.show()
-
-    return container
-
-#------------------------------------------------------------------------------
-
-def startInContainer(container, url, browserSettings = {}):
-    """Start a browser instance in the given container with the given URL."""
-    if os.name=="nt":
-        windowID = container.get_window().handle
-    else:
-        m = re.search("GtkVBox at 0x(\w+)", str(container))
-        hexID = m.group(1)
-        windowID = int(hexID, 16)
-
-    windowInfo = cefpython.WindowInfo()
-    windowInfo.SetAsChild(windowID)
-
-    return cefpython.CreateBrowserSync(windowInfo,
-                                       browserSettings = browserSettings,
-                                       navigateUrl = url)
-
-#------------------------------------------------------------------------------
-
-class OffscreenRenderHandler(object):
-    def GetRootScreenRect(self, browser, rect):
-        #print "GetRootScreenRect"
-        rect += [0, 0, 800, 600]
-        return True
-
-    def GetViewRect(self, browser, rect):
-        #print "GetViewRect"
-        rect += [0, 0, 800, 600]
-        return True
-
-    def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
-        #print "GetScreenPoint", viewX, viewY
-        rect += [viewX, viewY]
-        return True
-
-    def GetScreenInfo(self, browser, screenInfo):
-        #print "GetScreenInfo"
-        pass
-
-    def OnPopupShow(self, browser, show):
-        #print "OnPopupShow", show
-        pass
-
-    def OnPopupSize(self, browser, rect):
-        #print "OnPopupSize", rect
-        pass
-
-    def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
-        #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
-        pass
-
-    def OnCursorChange(self, browser, cursor):
-        #print "OnCursorChange", cursor
-        pass
-
-    def OnScrollOffsetChanged(self, browser):
-        #print "OnScrollOffsetChange"
-        pass
-
-    def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
-                      popupFeatures, windowInfo, client, browserSettings,
-                      noJavascriptAccess):
-        wInfo = cefpython.WindowInfo()
-        wInfo.SetAsOffscreen(int(0))
-
-        windowInfo.append(wInfo)
-
-        return False
-
-#------------------------------------------------------------------------------
-
-def initializeSimBrief():
-    """Initialize the (hidden) browser window for SimBrief."""
-    windowInfo = cefpython.WindowInfo()
-    windowInfo.SetAsOffscreen(int(0))
-
-    url = "file://" + os.path.join(_seleniumHandler.programDirectory,
-                                   "simbrief.html")
-    simBriefBrowser = cefpython.CreateBrowserSync(windowInfo,
-                                                  browserSettings = {},
-                                                  navigateUrl = url)
-    simBriefBrowser.SetClientHandler(OffscreenRenderHandler())
-
-    simBriefBrowser.SetFocus(True)
-
-#------------------------------------------------------------------------------
-
-def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
-    """Call SimBrief with the given plan."""
-    _seleniumHandler.callSimBrief(plan, getCredentials,
-                                  updateProgress, htmlFilePath)
-
-#------------------------------------------------------------------------------
-
-def finalize():
-    """Finalize the Chrome Embedded Framework."""
-    global _toQuit, _seleniumHandler
-    toQuit = True
-    _seleniumHandler.quit()
-    cefpython.Shutdown()
-
-#------------------------------------------------------------------------------
-
-def _handleTimeout():
-    """Handle the timeout by running the CEF message loop."""
-    if _toQuit:
-        return False
-    else:
-        cefpython.MessageLoopWork()
-        return True
-
-#------------------------------------------------------------------------------
-
-def _handleSizeAllocate(widget, sizeAlloc):
-    """Handle the size-allocate event."""
-    cefpython.WindowUtils.OnSize(widget.get_window().handle, 0, 0, 0)
Index: src/mlx/gui/checklist.py
===================================================================
--- src/mlx/gui/checklist.py	(revision 300)
+++ src/mlx/gui/checklist.py	(revision 300)
@@ -0,0 +1,380 @@
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+import mlx.config as config
+
+import os
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.checklist
+#
+# The checklist editor dialog.
+#
+# This module implements the checklist editor dialog. A checklist is a list of
+# sound files that are played one-by-one when requested by the user. The top
+# part of the dialog contains an aircraft type selector. Below it, on the left
+# there is a file selector, and the list of the files in the checklist to the
+# right. There are some buttons in the middle to manipulate the checklist.
+
+#------------------------------------------------------------------------------
+
+class ChecklistEditor(gtk.Dialog):
+    """The dialog to edit the checklists."""
+    def __init__(self, gui):
+        super(ChecklistEditor, self).__init__(WINDOW_TITLE_BASE + " - " +
+                                              xstr("chklst_title"),
+                                              gui.mainWindow,
+                                              DIALOG_MODAL)
+
+        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
+        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
+
+        self._gui = gui
+        self._checklists = {}
+        self._currentAircraftType = const.aircraftTypes[0]
+
+        contentArea = self.get_content_area()
+
+        typeBox = gtk.HBox()
+
+        label = gtk.Label(xstr("chklst_aircraftType"))
+        label.set_use_underline(True)
+
+        typeBox.pack_start(label, False, False, 4)
+
+        self._aircraftTypeModel = gtk.ListStore(str, int)
+        for type in const.aircraftTypes:
+            name = aircraftNames[type] if type in aircraftNames \
+                   else "Aircraft type #%d" % (type,)
+            self._aircraftTypeModel.append([name, type])
+        self._aircraftType = gtk.ComboBox(model = self._aircraftTypeModel)
+        renderer = gtk.CellRendererText()
+        self._aircraftType.pack_start(renderer, True)
+        self._aircraftType.add_attribute(renderer, "text", 0)
+        self._aircraftType.set_tooltip_text(xstr("chklst_aircraftType_tooltip"))
+        self._aircraftType.set_active(0)
+        self._aircraftType.connect("changed", self._aircraftTypeChanged)
+        label.set_mnemonic_widget(self._aircraftType)
+
+        typeBox.pack_start(self._aircraftType, True, True, 4)
+
+        typeBoxAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                         xscale = 0.0, yscale = 0.0)
+        typeBoxAlignment.set_size_request(400, -1)
+        typeBoxAlignment.add(typeBox)
+
+        contentArea.pack_start(typeBoxAlignment, False, False, 12)
+
+        fileBox = gtk.HBox()        
+
+        self._fileChooser = gtk.FileChooserWidget()
+        self._fileChooser.set_select_multiple(True)
+        
+        filter = gtk.FileFilter()
+        filter.set_name(xstr("file_filter_audio"))
+        filter.add_pattern("*.wav")
+        filter.add_pattern("*.mp3")
+        self._fileChooser.add_filter(filter)
+            
+        filter = gtk.FileFilter()
+        filter.set_name(xstr("file_filter_all"))
+        filter.add_pattern("*.*")
+        self._fileChooser.add_filter(filter)
+
+        self._fileChooser.connect("selection-changed",
+                                  self._fileChooserSelectionChanged)
+
+        fileBox.pack_start(self._fileChooser, True, True, 4)
+
+        controlBox = gtk.VBox()
+        controlAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                         xscale = 0.0, yscale = 0.0)
+        controlAlignment.set_padding(padding_top = 0, padding_bottom = 0,
+                                     padding_left = 32, padding_right = 32)
+        controlAlignment.add(controlBox)
+        fileBox.pack_start(controlAlignment, False, False, 0)
+
+        self._addButton = gtk.Button(xstr("chklst_add"))
+        self._addButton.set_use_underline(True)
+        self._addButton.set_tooltip_text(xstr("chklst_add_tooltip"))
+        self._addButton.connect("clicked", self._addButtonClicked)
+        addAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                     xscale = 0.0, yscale = 0.0)
+        addAlignment.set_padding(padding_top = 64, padding_bottom = 0,
+                                 padding_left = 0, padding_right = 0)
+        addAlignment.add(self._addButton)
+        controlBox.pack_start(addAlignment, False, False, 0)
+
+        self._removeButton = gtk.Button(xstr("chklst_remove"))
+        self._removeButton.set_use_underline(True)
+        self._removeButton.set_tooltip_text(xstr("chklst_remove_tooltip"))
+        self._removeButton.set_sensitive(False)
+        self._removeButton.connect("clicked", self._removeButtonClicked)
+
+        removeAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                        xscale = 0.0, yscale = 0.0)
+        removeAlignment.set_padding(padding_top = 64, padding_bottom = 0,
+                                    padding_left = 0, padding_right = 0)
+        removeAlignment.add(self._removeButton)
+        controlBox.pack_start(removeAlignment, False, False, 0)
+
+        self._moveUpButton = gtk.Button(xstr("chklst_moveUp"))
+        self._moveUpButton.set_use_underline(True)
+        self._moveUpButton.set_tooltip_text(xstr("chklst_moveUp_tooltip"))
+        self._moveUpButton.set_sensitive(False)
+        self._moveUpButton.connect("clicked", self._moveUpButtonClicked)
+
+        moveUpAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                        xscale = 0.0, yscale = 0.0)
+        moveUpAlignment.set_padding(padding_top = 16, padding_bottom = 0,
+                                    padding_left = 0, padding_right = 0)
+        moveUpAlignment.add(self._moveUpButton)
+        controlBox.pack_start(moveUpAlignment, False, False, 0)
+
+        self._moveDownButton = gtk.Button(xstr("chklst_moveDown"))
+        self._moveDownButton.set_use_underline(True)
+        self._moveDownButton.set_tooltip_text(xstr("chklst_moveDown_tooltip"))
+        self._moveDownButton.set_sensitive(False)
+        self._moveDownButton.connect("clicked", self._moveDownButtonClicked)
+
+        moveDownAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                        xscale = 0.0, yscale = 0.0)
+        moveDownAlignment.set_padding(padding_top = 4, padding_bottom = 0,
+                                    padding_left = 0, padding_right = 0)
+        moveDownAlignment.add(self._moveDownButton)
+        controlBox.pack_start(moveDownAlignment, False, False, 0)
+
+        self._fileListModel = gtk.ListStore(str, str)
+        self._fileList = gtk.TreeView(model = self._fileListModel)
+        self._fileList.connect("button-press-event",
+                               self._fileListButtonPressed)
+        column = gtk.TreeViewColumn(xstr("chklst_header"),
+                                    gtk.CellRendererText(), text = 0)
+        column.set_expand(True)
+        column.set_clickable(False)
+        column.set_reorderable(False)
+        self._fileList.append_column(column)
+        self._fileList.set_tooltip_column(1)
+        self._fileList.set_reorderable(True)
+        self._fileListPopupMenu = None
+        selection = self._fileList.get_selection()
+        selection.set_mode(SELECTION_MULTIPLE)
+        selection.connect("changed", self._fileListSelectionChanged)
+
+        self._buildFileListPopupMenu()
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._fileList)
+        scrolledWindow.set_size_request(200, -1)
+        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(SHADOW_IN)
+
+        fileBox.pack_start(scrolledWindow, False, False, 4)
+
+        contentArea.pack_start(fileBox, True, True, 4)
+
+        self.set_size_request(900, 500)
+
+    def run(self):
+        """Run the checklist editor dialog."""
+        self._checklists = {}
+        self._displayCurrentChecklist()
+        self.show_all()
+        response = super(ChecklistEditor, self).run()
+        self.hide()
+
+        if response==RESPONSETYPE_ACCEPT:
+            self._saveChecklist()
+            config = self._gui.config
+            for (aircraftType, checklist) in self._checklists.iteritems():
+                config.setChecklist(aircraftType, checklist)
+            config.save()
+
+    def _aircraftTypeChanged(self, comboBox):
+        """Called when the aircraft's type has changed."""        
+        self._saveChecklist()
+        self._displayCurrentChecklist()
+        
+    def _fileChooserSelectionChanged(self, fileChooser):
+        """Called when the selection of the given file chooser is changed."""
+        numFiles = 0
+        numSelected = 0
+        for path in fileChooser.get_filenames():
+            path = text2unicode(path)
+            numSelected += 1
+            if os.path.isfile(path): numFiles += 1
+
+        self._addButton.set_sensitive(numFiles>0 and numFiles==numSelected)
+
+    def _addButtonClicked(self, button):
+        """Called when the Add button is clicked."""
+        for path in self._fileChooser.get_filenames():
+            path = text2unicode(path)
+            self._fileListModel.append([os.path.basename(path),
+                                        path])
+        self._fileChooser.unselect_all()
+        
+    def _removeButtonClicked(self, button):
+        """Called when the Remove button is clicked."""
+        self._removeSelected()
+
+    def _removeSelected(self):
+        """Remove the currently selected files."""
+        selection = self._fileList.get_selection()
+        (model, paths) = selection.get_selected_rows()
+
+        iters = [model.get_iter(path) for path in paths]
+
+        for i in iters:
+            if i is not None:
+                model.remove(i)
+        
+    def _moveUpButtonClicked(self, button):
+        """Called when the move up button is clicked."""
+        self._moveSelected(True)
+        
+    def _moveDownButtonClicked(self, button):
+        """Called when the move down button is clicked."""
+        self._moveSelected(False)
+
+    def _moveSelected(self, up):
+        """Move the selected files up or down."""
+        selection = self._fileList.get_selection()
+        (model, paths) = selection.get_selected_rows()
+        indexes = [(path.get_indices() if pygobject else path)[0]
+                   for path in paths]        
+        indexes.sort()
+        if not up:
+            indexes.reverse()
+
+        for index in indexes:
+            fromIter = model.iter_nth_child(None, index)
+            toIter = model.iter_nth_child(None, index-1 if up else index + 1)
+            if up:
+                model.move_before(fromIter, toIter)
+            else:
+                model.move_after(fromIter, toIter)
+        
+        self._moveUpButton.set_sensitive(indexes[0]>1 if up else True)
+        numRows = model.iter_n_children(None)
+        self._moveDownButton.set_sensitive(True if up else
+                                           indexes[0]<(numRows-2))
+
+    def _fileListSelectionChanged(self, selection):
+        """Called when the selection in the file list changes."""
+        anySelected = selection.count_selected_rows()>0
+        self._removeButton.set_sensitive(anySelected)
+        self._popupRemoveItem.set_sensitive(anySelected)
+
+        if anySelected:
+            (model, paths) = selection.get_selected_rows()
+            minIndex = None
+            maxIndex = None
+            for path in paths:
+                [index] = path.get_indices() if pygobject else path
+                if minIndex is None or index<minIndex: minIndex = index
+                if maxIndex is None or index>maxIndex: maxIndex = index
+                        
+            self._moveUpButton.set_sensitive(minIndex>0)
+            self._popupMoveUpItem.set_sensitive(minIndex>0)
+
+            numRows = model.iter_n_children(None)
+            self._moveDownButton.set_sensitive(maxIndex<(numRows-1))
+            self._popupMoveDownItem.set_sensitive(maxIndex<(numRows-1))
+        else:
+            self._moveUpButton.set_sensitive(False)
+            self._popupMoveUpItem.set_sensitive(False)
+            self._moveDownButton.set_sensitive(False)
+            self._popupMoveDownItem.set_sensitive(False)
+
+    def _getAircraftType(self):
+        """Get the currently selected aircraft type."""
+        index = self._aircraftType.get_active()
+        return self._aircraftTypeModel[index][1]        
+
+    def _saveChecklist(self):
+        """Save the currently displayed checklist for the previously displayed
+        aircraft type."""
+        fileList = []
+        model = self._fileListModel
+        iter = model.get_iter_first()
+        while iter is not None:
+            path = model.get(iter, 1)[0]
+            fileList.append(path)
+            iter = model.iter_next(iter)
+
+        self._checklists[self._currentAircraftType] = config.Checklist(fileList)
+        
+    def _displayCurrentChecklist(self):
+        """Display the checklist for the currently selected aircraft type."""
+        aircraftType = self._getAircraftType()
+        self._currentAircraftType = aircraftType
+        if aircraftType not in self._checklists:
+            self._checklists[aircraftType] = \
+                self._gui.config.getChecklist(aircraftType).clone()
+        checklist = self._checklists[aircraftType]
+
+        self._fileListModel.clear()
+        for path in checklist:
+            self._fileListModel.append([os.path.basename(path), path])
+        
+    def _fileListButtonPressed(self, widget, event):
+        """Called when a mouse button is pressed on the file list."""
+        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
+            return
+
+        menu = self._fileListPopupMenu
+        if pygobject:
+            menu.popup(None, None, None, None, event.button, event.time)
+        else:
+            menu.popup(None, None, None, event.button, event.time)
+
+    def _buildFileListPopupMenu(self):
+        """Build the file list popup menu."""
+        menu = gtk.Menu()
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("chklst_remove"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupRemove)
+        menuItem.show()
+        self._popupRemoveItem = menuItem
+
+        menu.append(menuItem)
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("chklst_moveUp"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupMoveUp)
+        menuItem.show()
+        self._popupMoveUpItem = menuItem
+
+        menu.append(menuItem)
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("chklst_moveDown"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupMoveDown)
+        menuItem.show()
+        self._popupMoveDownItem = menuItem
+
+        menu.append(menuItem)
+
+        self._fileListPopupMenu = menu
+
+    def _popupRemove(self, menuItem):
+        """Remove the currently selected menu items."""
+        self._removeSelected()
+
+    def _popupMoveUp(self, menuItem):
+        """Move up the currently selected menu items."""
+        self._moveSelected(True)
+
+    def _popupMoveDown(self, menuItem):
+        """Move down the currently selected menu items."""
+        self._moveSelected(False)
+        
+#------------------------------------------------------------------------------
Index: src/mlx/gui/checklist.py
===================================================================
--- src/mlx/gui/checklist.py	(revision 300)
+++ 	(revision )
@@ -1,380 +1,0 @@
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-import mlx.config as config
-
-import os
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.checklist
-#
-# The checklist editor dialog.
-#
-# This module implements the checklist editor dialog. A checklist is a list of
-# sound files that are played one-by-one when requested by the user. The top
-# part of the dialog contains an aircraft type selector. Below it, on the left
-# there is a file selector, and the list of the files in the checklist to the
-# right. There are some buttons in the middle to manipulate the checklist.
-
-#------------------------------------------------------------------------------
-
-class ChecklistEditor(gtk.Dialog):
-    """The dialog to edit the checklists."""
-    def __init__(self, gui):
-        super(ChecklistEditor, self).__init__(WINDOW_TITLE_BASE + " - " +
-                                              xstr("chklst_title"),
-                                              gui.mainWindow,
-                                              DIALOG_MODAL)
-
-        self.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
-        self.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
-
-        self._gui = gui
-        self._checklists = {}
-        self._currentAircraftType = const.aircraftTypes[0]
-
-        contentArea = self.get_content_area()
-
-        typeBox = gtk.HBox()
-
-        label = gtk.Label(xstr("chklst_aircraftType"))
-        label.set_use_underline(True)
-
-        typeBox.pack_start(label, False, False, 4)
-
-        self._aircraftTypeModel = gtk.ListStore(str, int)
-        for type in const.aircraftTypes:
-            name = aircraftNames[type] if type in aircraftNames \
-                   else "Aircraft type #%d" % (type,)
-            self._aircraftTypeModel.append([name, type])
-        self._aircraftType = gtk.ComboBox(model = self._aircraftTypeModel)
-        renderer = gtk.CellRendererText()
-        self._aircraftType.pack_start(renderer, True)
-        self._aircraftType.add_attribute(renderer, "text", 0)
-        self._aircraftType.set_tooltip_text(xstr("chklst_aircraftType_tooltip"))
-        self._aircraftType.set_active(0)
-        self._aircraftType.connect("changed", self._aircraftTypeChanged)
-        label.set_mnemonic_widget(self._aircraftType)
-
-        typeBox.pack_start(self._aircraftType, True, True, 4)
-
-        typeBoxAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                         xscale = 0.0, yscale = 0.0)
-        typeBoxAlignment.set_size_request(400, -1)
-        typeBoxAlignment.add(typeBox)
-
-        contentArea.pack_start(typeBoxAlignment, False, False, 12)
-
-        fileBox = gtk.HBox()        
-
-        self._fileChooser = gtk.FileChooserWidget()
-        self._fileChooser.set_select_multiple(True)
-        
-        filter = gtk.FileFilter()
-        filter.set_name(xstr("file_filter_audio"))
-        filter.add_pattern("*.wav")
-        filter.add_pattern("*.mp3")
-        self._fileChooser.add_filter(filter)
-            
-        filter = gtk.FileFilter()
-        filter.set_name(xstr("file_filter_all"))
-        filter.add_pattern("*.*")
-        self._fileChooser.add_filter(filter)
-
-        self._fileChooser.connect("selection-changed",
-                                  self._fileChooserSelectionChanged)
-
-        fileBox.pack_start(self._fileChooser, True, True, 4)
-
-        controlBox = gtk.VBox()
-        controlAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                         xscale = 0.0, yscale = 0.0)
-        controlAlignment.set_padding(padding_top = 0, padding_bottom = 0,
-                                     padding_left = 32, padding_right = 32)
-        controlAlignment.add(controlBox)
-        fileBox.pack_start(controlAlignment, False, False, 0)
-
-        self._addButton = gtk.Button(xstr("chklst_add"))
-        self._addButton.set_use_underline(True)
-        self._addButton.set_tooltip_text(xstr("chklst_add_tooltip"))
-        self._addButton.connect("clicked", self._addButtonClicked)
-        addAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                     xscale = 0.0, yscale = 0.0)
-        addAlignment.set_padding(padding_top = 64, padding_bottom = 0,
-                                 padding_left = 0, padding_right = 0)
-        addAlignment.add(self._addButton)
-        controlBox.pack_start(addAlignment, False, False, 0)
-
-        self._removeButton = gtk.Button(xstr("chklst_remove"))
-        self._removeButton.set_use_underline(True)
-        self._removeButton.set_tooltip_text(xstr("chklst_remove_tooltip"))
-        self._removeButton.set_sensitive(False)
-        self._removeButton.connect("clicked", self._removeButtonClicked)
-
-        removeAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                        xscale = 0.0, yscale = 0.0)
-        removeAlignment.set_padding(padding_top = 64, padding_bottom = 0,
-                                    padding_left = 0, padding_right = 0)
-        removeAlignment.add(self._removeButton)
-        controlBox.pack_start(removeAlignment, False, False, 0)
-
-        self._moveUpButton = gtk.Button(xstr("chklst_moveUp"))
-        self._moveUpButton.set_use_underline(True)
-        self._moveUpButton.set_tooltip_text(xstr("chklst_moveUp_tooltip"))
-        self._moveUpButton.set_sensitive(False)
-        self._moveUpButton.connect("clicked", self._moveUpButtonClicked)
-
-        moveUpAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                        xscale = 0.0, yscale = 0.0)
-        moveUpAlignment.set_padding(padding_top = 16, padding_bottom = 0,
-                                    padding_left = 0, padding_right = 0)
-        moveUpAlignment.add(self._moveUpButton)
-        controlBox.pack_start(moveUpAlignment, False, False, 0)
-
-        self._moveDownButton = gtk.Button(xstr("chklst_moveDown"))
-        self._moveDownButton.set_use_underline(True)
-        self._moveDownButton.set_tooltip_text(xstr("chklst_moveDown_tooltip"))
-        self._moveDownButton.set_sensitive(False)
-        self._moveDownButton.connect("clicked", self._moveDownButtonClicked)
-
-        moveDownAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                        xscale = 0.0, yscale = 0.0)
-        moveDownAlignment.set_padding(padding_top = 4, padding_bottom = 0,
-                                    padding_left = 0, padding_right = 0)
-        moveDownAlignment.add(self._moveDownButton)
-        controlBox.pack_start(moveDownAlignment, False, False, 0)
-
-        self._fileListModel = gtk.ListStore(str, str)
-        self._fileList = gtk.TreeView(model = self._fileListModel)
-        self._fileList.connect("button-press-event",
-                               self._fileListButtonPressed)
-        column = gtk.TreeViewColumn(xstr("chklst_header"),
-                                    gtk.CellRendererText(), text = 0)
-        column.set_expand(True)
-        column.set_clickable(False)
-        column.set_reorderable(False)
-        self._fileList.append_column(column)
-        self._fileList.set_tooltip_column(1)
-        self._fileList.set_reorderable(True)
-        self._fileListPopupMenu = None
-        selection = self._fileList.get_selection()
-        selection.set_mode(SELECTION_MULTIPLE)
-        selection.connect("changed", self._fileListSelectionChanged)
-
-        self._buildFileListPopupMenu()
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._fileList)
-        scrolledWindow.set_size_request(200, -1)
-        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(SHADOW_IN)
-
-        fileBox.pack_start(scrolledWindow, False, False, 4)
-
-        contentArea.pack_start(fileBox, True, True, 4)
-
-        self.set_size_request(900, 500)
-
-    def run(self):
-        """Run the checklist editor dialog."""
-        self._checklists = {}
-        self._displayCurrentChecklist()
-        self.show_all()
-        response = super(ChecklistEditor, self).run()
-        self.hide()
-
-        if response==RESPONSETYPE_ACCEPT:
-            self._saveChecklist()
-            config = self._gui.config
-            for (aircraftType, checklist) in self._checklists.iteritems():
-                config.setChecklist(aircraftType, checklist)
-            config.save()
-
-    def _aircraftTypeChanged(self, comboBox):
-        """Called when the aircraft's type has changed."""        
-        self._saveChecklist()
-        self._displayCurrentChecklist()
-        
-    def _fileChooserSelectionChanged(self, fileChooser):
-        """Called when the selection of the given file chooser is changed."""
-        numFiles = 0
-        numSelected = 0
-        for path in fileChooser.get_filenames():
-            path = text2unicode(path)
-            numSelected += 1
-            if os.path.isfile(path): numFiles += 1
-
-        self._addButton.set_sensitive(numFiles>0 and numFiles==numSelected)
-
-    def _addButtonClicked(self, button):
-        """Called when the Add button is clicked."""
-        for path in self._fileChooser.get_filenames():
-            path = text2unicode(path)
-            self._fileListModel.append([os.path.basename(path),
-                                        path])
-        self._fileChooser.unselect_all()
-        
-    def _removeButtonClicked(self, button):
-        """Called when the Remove button is clicked."""
-        self._removeSelected()
-
-    def _removeSelected(self):
-        """Remove the currently selected files."""
-        selection = self._fileList.get_selection()
-        (model, paths) = selection.get_selected_rows()
-
-        iters = [model.get_iter(path) for path in paths]
-
-        for i in iters:
-            if i is not None:
-                model.remove(i)
-        
-    def _moveUpButtonClicked(self, button):
-        """Called when the move up button is clicked."""
-        self._moveSelected(True)
-        
-    def _moveDownButtonClicked(self, button):
-        """Called when the move down button is clicked."""
-        self._moveSelected(False)
-
-    def _moveSelected(self, up):
-        """Move the selected files up or down."""
-        selection = self._fileList.get_selection()
-        (model, paths) = selection.get_selected_rows()
-        indexes = [(path.get_indices() if pygobject else path)[0]
-                   for path in paths]        
-        indexes.sort()
-        if not up:
-            indexes.reverse()
-
-        for index in indexes:
-            fromIter = model.iter_nth_child(None, index)
-            toIter = model.iter_nth_child(None, index-1 if up else index + 1)
-            if up:
-                model.move_before(fromIter, toIter)
-            else:
-                model.move_after(fromIter, toIter)
-        
-        self._moveUpButton.set_sensitive(indexes[0]>1 if up else True)
-        numRows = model.iter_n_children(None)
-        self._moveDownButton.set_sensitive(True if up else
-                                           indexes[0]<(numRows-2))
-
-    def _fileListSelectionChanged(self, selection):
-        """Called when the selection in the file list changes."""
-        anySelected = selection.count_selected_rows()>0
-        self._removeButton.set_sensitive(anySelected)
-        self._popupRemoveItem.set_sensitive(anySelected)
-
-        if anySelected:
-            (model, paths) = selection.get_selected_rows()
-            minIndex = None
-            maxIndex = None
-            for path in paths:
-                [index] = path.get_indices() if pygobject else path
-                if minIndex is None or index<minIndex: minIndex = index
-                if maxIndex is None or index>maxIndex: maxIndex = index
-                        
-            self._moveUpButton.set_sensitive(minIndex>0)
-            self._popupMoveUpItem.set_sensitive(minIndex>0)
-
-            numRows = model.iter_n_children(None)
-            self._moveDownButton.set_sensitive(maxIndex<(numRows-1))
-            self._popupMoveDownItem.set_sensitive(maxIndex<(numRows-1))
-        else:
-            self._moveUpButton.set_sensitive(False)
-            self._popupMoveUpItem.set_sensitive(False)
-            self._moveDownButton.set_sensitive(False)
-            self._popupMoveDownItem.set_sensitive(False)
-
-    def _getAircraftType(self):
-        """Get the currently selected aircraft type."""
-        index = self._aircraftType.get_active()
-        return self._aircraftTypeModel[index][1]        
-
-    def _saveChecklist(self):
-        """Save the currently displayed checklist for the previously displayed
-        aircraft type."""
-        fileList = []
-        model = self._fileListModel
-        iter = model.get_iter_first()
-        while iter is not None:
-            path = model.get(iter, 1)[0]
-            fileList.append(path)
-            iter = model.iter_next(iter)
-
-        self._checklists[self._currentAircraftType] = config.Checklist(fileList)
-        
-    def _displayCurrentChecklist(self):
-        """Display the checklist for the currently selected aircraft type."""
-        aircraftType = self._getAircraftType()
-        self._currentAircraftType = aircraftType
-        if aircraftType not in self._checklists:
-            self._checklists[aircraftType] = \
-                self._gui.config.getChecklist(aircraftType).clone()
-        checklist = self._checklists[aircraftType]
-
-        self._fileListModel.clear()
-        for path in checklist:
-            self._fileListModel.append([os.path.basename(path), path])
-        
-    def _fileListButtonPressed(self, widget, event):
-        """Called when a mouse button is pressed on the file list."""
-        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
-            return
-
-        menu = self._fileListPopupMenu
-        if pygobject:
-            menu.popup(None, None, None, None, event.button, event.time)
-        else:
-            menu.popup(None, None, None, event.button, event.time)
-
-    def _buildFileListPopupMenu(self):
-        """Build the file list popup menu."""
-        menu = gtk.Menu()
-
-        menuItem = gtk.MenuItem()
-        menuItem.set_label(xstr("chklst_remove"))
-        menuItem.set_use_underline(True)
-        menuItem.connect("activate", self._popupRemove)
-        menuItem.show()
-        self._popupRemoveItem = menuItem
-
-        menu.append(menuItem)
-
-        menuItem = gtk.MenuItem()
-        menuItem.set_label(xstr("chklst_moveUp"))
-        menuItem.set_use_underline(True)
-        menuItem.connect("activate", self._popupMoveUp)
-        menuItem.show()
-        self._popupMoveUpItem = menuItem
-
-        menu.append(menuItem)
-
-        menuItem = gtk.MenuItem()
-        menuItem.set_label(xstr("chklst_moveDown"))
-        menuItem.set_use_underline(True)
-        menuItem.connect("activate", self._popupMoveDown)
-        menuItem.show()
-        self._popupMoveDownItem = menuItem
-
-        menu.append(menuItem)
-
-        self._fileListPopupMenu = menu
-
-    def _popupRemove(self, menuItem):
-        """Remove the currently selected menu items."""
-        self._removeSelected()
-
-    def _popupMoveUp(self, menuItem):
-        """Move up the currently selected menu items."""
-        self._moveSelected(True)
-
-    def _popupMoveDown(self, menuItem):
-        """Move down the currently selected menu items."""
-        self._moveSelected(False)
-        
-#------------------------------------------------------------------------------
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 682)
+++ src/mlx/gui/common.py	(revision 682)
@@ -0,0 +1,357 @@
+
+from mlx.common import *
+
+import mlx.const as _const
+from mlx.i18n import xstr
+
+from mlx.util import secondaryInstallation
+
+import os
+
+#-----------------------------------------------------------------------------
+
+## @package mlx.gui.common
+#
+# Common definitions and utilities for the GUI
+#
+# The main purpose of this module is to provide common definitions for things
+# that are named differently in Gtk+ 2 and 3. This way the other parts of the
+# GUI have to check the version in use very rarely. The variable \c pygobject
+# tells which version is being used. If it is \c True, Gtk+ 3 is used via the
+# PyGObject interface. Otherwise Gtk+ 2 is used, which is the default on
+# Windows or when the \c FORCE_PYGTK environment variable is set.
+#
+# Besides this there are some common utility classes and functions.
+
+#-----------------------------------------------------------------------------
+
+appIndicator = False
+
+if not pygobject:
+    print "Using PyGTK"
+    pygobject = False
+    import pygtk
+    pygtk.require("2.0")
+    import gtk.gdk as gdk
+    import gtk
+    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
+    JUSTIFY_LEFT = gtk.JUSTIFY_LEFT
+
+    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
+
+    SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
+
+    SHADOW_IN = gtk.SHADOW_IN
+    SHADOW_NONE = gtk.SHADOW_NONE
+
+    POLICY_AUTOMATIC = gtk.POLICY_AUTOMATIC
+    POLICY_NEVER = gtk.POLICY_NEVER
+    POLICY_ALWAYS = gtk.POLICY_ALWAYS
+
+    WEIGHT_NORMAL = pango.WEIGHT_NORMAL
+    WEIGHT_BOLD = pango.WEIGHT_BOLD
+
+    WINDOW_STATE_ICONIFIED = gdk.WINDOW_STATE_ICONIFIED
+    WINDOW_STATE_WITHDRAWN = gdk.WINDOW_STATE_WITHDRAWN
+
+    SORT_ASCENDING = gtk.SORT_ASCENDING
+    SORT_DESCENDING = gtk.SORT_DESCENDING
+
+    EVENT_BUTTON_PRESS = gdk.BUTTON_PRESS
+
+    TREE_VIEW_COLUMN_FIXED = gtk.TREE_VIEW_COLUMN_FIXED
+
+    FILL = gtk.FILL
+    EXPAND = gtk.EXPAND
+
+    UPDATE_IF_VALID = gtk.UPDATE_IF_VALID
+
+    pixbuf_new_from_file = gdk.pixbuf_new_from_file
+
+    def text2unicode(text):
+        """Convert the given text, returned by a Gtk widget, to Unicode."""
+        return unicode(text)
+
+    def text2str(text):
+        """Convert the given text, returned by xstr to a string."""
+        return str(text)
+
+else: # pygobject
+    from gi.repository import Gdk as gdk
+    from gi.repository import GdkPixbuf as gdkPixbuf
+    from gi.repository import Gtk as gtk
+    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
+    JUSTIFY_LEFT = gtk.Justification.LEFT
+
+    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
+
+    SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
+
+    SHADOW_IN = gtk.ShadowType.IN
+    SHADOW_NONE = gtk.ShadowType.NONE
+
+    POLICY_AUTOMATIC = gtk.PolicyType.AUTOMATIC
+    POLICY_NEVER = gtk.PolicyType.NEVER
+    POLICY_ALWAYS = gtk.PolicyType.ALWAYS
+
+    WEIGHT_NORMAL = pango.Weight.NORMAL
+    WEIGHT_BOLD = pango.Weight.BOLD
+
+    WINDOW_STATE_ICONIFIED = gdk.WindowState.ICONIFIED
+    WINDOW_STATE_WITHDRAWN = gdk.WindowState.WITHDRAWN
+
+    SORT_ASCENDING = gtk.SortType.ASCENDING
+    SORT_DESCENDING = gtk.SortType.DESCENDING
+
+    EVENT_BUTTON_PRESS = gdk.EventType.BUTTON_PRESS
+
+    TREE_VIEW_COLUMN_FIXED = gtk.TreeViewColumnSizing.FIXED
+
+    FILL = gtk.AttachOptions.FILL
+    EXPAND = gtk.AttachOptions.EXPAND
+
+    UPDATE_IF_VALID = gtk.SpinButtonUpdatePolicy.IF_VALID
+
+    pixbuf_new_from_file = gdkPixbuf.Pixbuf.new_from_file
+
+    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]
+
+    def text2str(text):
+        """Convert the given text, returned by xstr to a string."""
+        return _utf8Decoder(text)[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 reset(self):
+        """Reset the integer."""
+        self.set_int(None)
+
+    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,))
+
+#------------------------------------------------------------------------------
+
+PROGRAM_NAME = "MAVA Logger X"
+
+WINDOW_TITLE_BASE = PROGRAM_NAME + " " + _const.VERSION
+if secondaryInstallation:
+    WINDOW_TITLE_BASE += " (" + xstr("secondary") + ")"
+
+#------------------------------------------------------------------------------
+
+# A mapping of aircraft types to their screen names
+aircraftNames = { _const.AIRCRAFT_B736  : xstr("aircraft_b736"),
+                  _const.AIRCRAFT_B737  : xstr("aircraft_b737"),
+                  _const.AIRCRAFT_B738  : xstr("aircraft_b738"),
+                  _const.AIRCRAFT_B738C : xstr("aircraft_b738c"),
+                  _const.AIRCRAFT_B733  : xstr("aircraft_b733"),
+                  _const.AIRCRAFT_B734  : xstr("aircraft_b734"),
+                  _const.AIRCRAFT_B735  : xstr("aircraft_b735"),
+                  _const.AIRCRAFT_DH8D  : xstr("aircraft_dh8d"),
+                  _const.AIRCRAFT_B762  : xstr("aircraft_b762"),
+                  _const.AIRCRAFT_B763  : xstr("aircraft_b763"),
+                  _const.AIRCRAFT_CRJ2  : xstr("aircraft_crj2"),
+                  _const.AIRCRAFT_F70   : xstr("aircraft_f70"),
+                  _const.AIRCRAFT_DC3   : xstr("aircraft_dc3"),
+                  _const.AIRCRAFT_T134  : xstr("aircraft_t134"),
+                  _const.AIRCRAFT_T154  : xstr("aircraft_t154"),
+                  _const.AIRCRAFT_YK40  : xstr("aircraft_yk40"),
+                  _const.AIRCRAFT_B462  : xstr("aircraft_b462") }
+
+#------------------------------------------------------------------------------
+
+def formatFlightLogLine(timeStr, line):
+    """Format the given flight log line."""
+    """Format the given line for flight logging."""
+    if timeStr is not None:
+        line = timeStr + ": " + line
+    return line + "\n"
+
+#------------------------------------------------------------------------------
+
+def addFaultTag(buffer):
+    """Add a tag named 'fault' to the given buffer."""
+    buffer.create_tag("fault", foreground="red", weight=WEIGHT_BOLD)
+
+#------------------------------------------------------------------------------
+
+def appendTextBuffer(buffer, text, isFault = False):
+    """Append the given line at the end of the given text buffer.
+
+    If isFault is set, use the tag named 'fault'."""
+    insertTextBuffer(buffer, buffer.get_end_iter(), text, isFault)
+
+#------------------------------------------------------------------------------
+
+def insertTextBuffer(buffer, iter, text, isFault = False):
+    """Insert the given line into the given text buffer at the given iterator.
+
+    If isFault is set, use the tag named 'fault' else use the tag named
+    'normal'."""
+    line = iter.get_line()
+
+    buffer.insert(iter, text)
+
+    iter0 = buffer.get_iter_at_line(line)
+    iter1 = buffer.get_iter_at_line(line+1)
+    if isFault:
+        buffer.apply_tag_by_name("fault", iter0, iter1)
+    else:
+        buffer.remove_all_tags(iter0, iter1)
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/common.py
===================================================================
--- src/mlx/gui/common.py	(revision 682)
+++ 	(revision )
@@ -1,357 +1,0 @@
-
-from mlx.common import *
-
-import mlx.const as _const
-from mlx.i18n import xstr
-
-from mlx.util import secondaryInstallation
-
-import os
-
-#-----------------------------------------------------------------------------
-
-## @package mlx.gui.common
-#
-# Common definitions and utilities for the GUI
-#
-# The main purpose of this module is to provide common definitions for things
-# that are named differently in Gtk+ 2 and 3. This way the other parts of the
-# GUI have to check the version in use very rarely. The variable \c pygobject
-# tells which version is being used. If it is \c True, Gtk+ 3 is used via the
-# PyGObject interface. Otherwise Gtk+ 2 is used, which is the default on
-# Windows or when the \c FORCE_PYGTK environment variable is set.
-#
-# Besides this there are some common utility classes and functions.
-
-#-----------------------------------------------------------------------------
-
-appIndicator = False
-
-if not pygobject:
-    print "Using PyGTK"
-    pygobject = False
-    import pygtk
-    pygtk.require("2.0")
-    import gtk.gdk as gdk
-    import gtk
-    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
-    JUSTIFY_LEFT = gtk.JUSTIFY_LEFT
-
-    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
-
-    SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
-
-    SHADOW_IN = gtk.SHADOW_IN
-    SHADOW_NONE = gtk.SHADOW_NONE
-
-    POLICY_AUTOMATIC = gtk.POLICY_AUTOMATIC
-    POLICY_NEVER = gtk.POLICY_NEVER
-    POLICY_ALWAYS = gtk.POLICY_ALWAYS
-
-    WEIGHT_NORMAL = pango.WEIGHT_NORMAL
-    WEIGHT_BOLD = pango.WEIGHT_BOLD
-
-    WINDOW_STATE_ICONIFIED = gdk.WINDOW_STATE_ICONIFIED
-    WINDOW_STATE_WITHDRAWN = gdk.WINDOW_STATE_WITHDRAWN
-
-    SORT_ASCENDING = gtk.SORT_ASCENDING
-    SORT_DESCENDING = gtk.SORT_DESCENDING
-
-    EVENT_BUTTON_PRESS = gdk.BUTTON_PRESS
-
-    TREE_VIEW_COLUMN_FIXED = gtk.TREE_VIEW_COLUMN_FIXED
-
-    FILL = gtk.FILL
-    EXPAND = gtk.EXPAND
-
-    UPDATE_IF_VALID = gtk.UPDATE_IF_VALID
-
-    pixbuf_new_from_file = gdk.pixbuf_new_from_file
-
-    def text2unicode(text):
-        """Convert the given text, returned by a Gtk widget, to Unicode."""
-        return unicode(text)
-
-    def text2str(text):
-        """Convert the given text, returned by xstr to a string."""
-        return str(text)
-
-else: # pygobject
-    from gi.repository import Gdk as gdk
-    from gi.repository import GdkPixbuf as gdkPixbuf
-    from gi.repository import Gtk as gtk
-    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
-    JUSTIFY_LEFT = gtk.Justification.LEFT
-
-    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
-
-    SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
-
-    SHADOW_IN = gtk.ShadowType.IN
-    SHADOW_NONE = gtk.ShadowType.NONE
-
-    POLICY_AUTOMATIC = gtk.PolicyType.AUTOMATIC
-    POLICY_NEVER = gtk.PolicyType.NEVER
-    POLICY_ALWAYS = gtk.PolicyType.ALWAYS
-
-    WEIGHT_NORMAL = pango.Weight.NORMAL
-    WEIGHT_BOLD = pango.Weight.BOLD
-
-    WINDOW_STATE_ICONIFIED = gdk.WindowState.ICONIFIED
-    WINDOW_STATE_WITHDRAWN = gdk.WindowState.WITHDRAWN
-
-    SORT_ASCENDING = gtk.SortType.ASCENDING
-    SORT_DESCENDING = gtk.SortType.DESCENDING
-
-    EVENT_BUTTON_PRESS = gdk.EventType.BUTTON_PRESS
-
-    TREE_VIEW_COLUMN_FIXED = gtk.TreeViewColumnSizing.FIXED
-
-    FILL = gtk.AttachOptions.FILL
-    EXPAND = gtk.AttachOptions.EXPAND
-
-    UPDATE_IF_VALID = gtk.SpinButtonUpdatePolicy.IF_VALID
-
-    pixbuf_new_from_file = gdkPixbuf.Pixbuf.new_from_file
-
-    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]
-
-    def text2str(text):
-        """Convert the given text, returned by xstr to a string."""
-        return _utf8Decoder(text)[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 reset(self):
-        """Reset the integer."""
-        self.set_int(None)
-
-    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,))
-
-#------------------------------------------------------------------------------
-
-PROGRAM_NAME = "MAVA Logger X"
-
-WINDOW_TITLE_BASE = PROGRAM_NAME + " " + _const.VERSION
-if secondaryInstallation:
-    WINDOW_TITLE_BASE += " (" + xstr("secondary") + ")"
-
-#------------------------------------------------------------------------------
-
-# A mapping of aircraft types to their screen names
-aircraftNames = { _const.AIRCRAFT_B736  : xstr("aircraft_b736"),
-                  _const.AIRCRAFT_B737  : xstr("aircraft_b737"),
-                  _const.AIRCRAFT_B738  : xstr("aircraft_b738"),
-                  _const.AIRCRAFT_B738C : xstr("aircraft_b738c"),
-                  _const.AIRCRAFT_B733  : xstr("aircraft_b733"),
-                  _const.AIRCRAFT_B734  : xstr("aircraft_b734"),
-                  _const.AIRCRAFT_B735  : xstr("aircraft_b735"),
-                  _const.AIRCRAFT_DH8D  : xstr("aircraft_dh8d"),
-                  _const.AIRCRAFT_B762  : xstr("aircraft_b762"),
-                  _const.AIRCRAFT_B763  : xstr("aircraft_b763"),
-                  _const.AIRCRAFT_CRJ2  : xstr("aircraft_crj2"),
-                  _const.AIRCRAFT_F70   : xstr("aircraft_f70"),
-                  _const.AIRCRAFT_DC3   : xstr("aircraft_dc3"),
-                  _const.AIRCRAFT_T134  : xstr("aircraft_t134"),
-                  _const.AIRCRAFT_T154  : xstr("aircraft_t154"),
-                  _const.AIRCRAFT_YK40  : xstr("aircraft_yk40"),
-                  _const.AIRCRAFT_B462  : xstr("aircraft_b462") }
-
-#------------------------------------------------------------------------------
-
-def formatFlightLogLine(timeStr, line):
-    """Format the given flight log line."""
-    """Format the given line for flight logging."""
-    if timeStr is not None:
-        line = timeStr + ": " + line
-    return line + "\n"
-
-#------------------------------------------------------------------------------
-
-def addFaultTag(buffer):
-    """Add a tag named 'fault' to the given buffer."""
-    buffer.create_tag("fault", foreground="red", weight=WEIGHT_BOLD)
-
-#------------------------------------------------------------------------------
-
-def appendTextBuffer(buffer, text, isFault = False):
-    """Append the given line at the end of the given text buffer.
-
-    If isFault is set, use the tag named 'fault'."""
-    insertTextBuffer(buffer, buffer.get_end_iter(), text, isFault)
-
-#------------------------------------------------------------------------------
-
-def insertTextBuffer(buffer, iter, text, isFault = False):
-    """Insert the given line into the given text buffer at the given iterator.
-
-    If isFault is set, use the tag named 'fault' else use the tag named
-    'normal'."""
-    line = iter.get_line()
-
-    buffer.insert(iter, text)
-
-    iter0 = buffer.get_iter_at_line(line)
-    iter1 = buffer.get_iter_at_line(line+1)
-    if isFault:
-        buffer.apply_tag_by_name("fault", iter0, iter1)
-    else:
-        buffer.remove_all_tags(iter0, iter1)
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/delaycodes.py
===================================================================
--- src/mlx/gui/delaycodes.py	(revision 555)
+++ src/mlx/gui/delaycodes.py	(revision 555)
@@ -0,0 +1,385 @@
+# Module to handle the GUI aspects of the table of the delay codes
+
+#------------------------------------------------------------------------------
+
+from dcdata import CAPTION, DELAYCODE, getTable
+
+from mlx.gui.common import *
+
+import mlx.const as const
+
+#------------------------------------------------------------------------------
+
+if pygobject:
+
+#------------------------------------------------------------------------------
+
+    class Viewport(gtk.Viewport):
+        """Viewport implementation that alleviates the problem with improper
+        resizing by the VBox."""
+        def __init__(self):
+            """Construct the viewport."""
+            gtk.Viewport.__init__(self)
+            self._recursive = False
+            self._vboxHeight = None
+
+        def setVBOXHeight(self, vboxHeight):
+            """Set the height of the VBox which will be used to calculate the
+            viewport's height."""
+            self._vboxHeight = vboxHeight
+
+        def do_size_allocate(self, allocation):
+            """Called when the viewport's size is allocated.
+
+            The height in the allocation object is modified so that it is only
+            so high to fit into the VBox."""
+            if self._vboxHeight is not None:
+                allocation.height = self._vboxHeight - allocation.y
+                self._vboxHeight = None
+            gtk.Viewport.do_size_allocate(self, allocation)
+
+    class DelayCodeTableBase(gtk.VBox, gtk.Scrollable):
+        """PyGObject-specific base class for the delay code table."""
+        __gproperties__ = {
+            "vscroll-policy" : ( gtk.ScrollablePolicy,
+                                 "vscroll-policy",
+                                 "The vertical scrolling policy",
+                                 gtk.ScrollablePolicy.MINIMUM,
+                                 gobject.PARAM_READWRITE ),
+            "vadjustment" : ( gtk.Adjustment,
+                              "vadjustment",
+                              "The vertical adjustment",
+                              gobject.PARAM_READWRITE ),
+            "hscroll-policy" : ( gtk.ScrollablePolicy,
+                                 "hscroll-policy",
+                                 "The horizontal scrolling policy",
+                                 gtk.ScrollablePolicy.MINIMUM,
+                                 gobject.PARAM_READWRITE ),
+            "hadjustment" : ( gtk.Adjustment,
+                              "hadjustment",
+                              "The horizontal adjustment",
+                              gobject.PARAM_READWRITE )  }
+
+
+        @staticmethod
+        def _createViewport():
+            """Create an instance of the viewport class used by this base class."""
+            return Viewport()
+
+        def __init__(self):
+            """Construct the delay code table."""
+            super(DelayCodeTableBase, self).__init__()
+
+        def do_size_allocate(self, allocation):
+            """Allocate the size for the table and its children.
+
+            This sets up the VBox height in the viewport and then calls the
+            do_size_allocate() function of VBox()."""
+            self._viewport.setVBOXHeight(allocation.height)
+            gtk.VBox.do_size_allocate(self, allocation)
+            self.allocate_column_sizes(allocation)
+
+        def do_get_property(self, prop):
+            """Get the value of one of the properties defined above.
+
+            The request is forwarded to the viewport."""
+            if prop.name=="vscroll-policy":
+                return self._viewport.get_vscroll_policy()
+            elif prop.name=="hscroll-policy":
+                return self._viewport.get_hscroll_policy()
+            elif prop.name=="vadjustment":
+                return self._viewport.get_vadjustment()
+            elif prop.name=="hadjustment":
+                return self._viewport.get_hadjustment()
+            else:
+                raise AttributeError("mlx.gui.delaycodes.DelayCodeTableBase: property %s is not handled in do_get_property" %
+                                     (prop.name,))
+
+        def do_set_property(self, prop, value):
+            """Set the value of the adjustment properties defined above.
+
+            The adjustments are forwarded to the viewport."""
+            if prop.name=="vadjustment":
+                self._viewport.set_vadjustment(value)
+            elif prop.name=="hadjustment":
+                self._viewport.set_hadjustment(value)
+                self._treeView.set_hadjustment(value)
+            else:
+                raise AttributeError("mlx.gui.delaycodes.DelayCodeTableBase: property %s is not handled in do_set_property" %
+                                     (prop.name,))
+
+        def setStyle(self):
+            """Set the style of the event box from the treeview."""
+
+    class Alignment(gtk.Alignment):
+        """An alignment that remembers the width it was given."""
+        def __init__(self, xalign = 0.0, yalign=0.0,
+                     xscale = 0.0, yscale = 0.0 ):
+            """Construct the alignment."""
+            super(Alignment, self).__init__(xalign = xalign, yalign = yalign,
+                                            xscale = xscale, yscale = yscale)
+            self.allocatedWidth = 0
+
+        def do_size_allocate(self, allocation):
+            """Called with the new size allocation."""
+            self.allocatedWidth = allocation.width
+            gtk.Alignment.do_size_allocate(self, allocation)
+
+#------------------------------------------------------------------------------
+
+else: # pygobject
+
+#------------------------------------------------------------------------------
+
+    class DelayCodeTableBase (gtk.VBox):
+        """Base class of the delay code table for PyGtk."""
+
+        __gsignals__ = {
+            "set-scroll-adjustments": (
+                gobject.SIGNAL_RUN_LAST,
+                gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment))
+                }
+
+        @staticmethod
+        def _createViewport():
+            """Create an instance of the viewport class used by this base class."""
+            return gtk.Viewport()
+
+        def __init__(self):
+            """Construct the base class."""
+            super(DelayCodeTableBase, self).__init__()
+            self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
+            self.connect("size-allocate", self._do_size_allocate)
+
+        def do_set_scroll_adjustments(self, hAdjustment, vAdjustment):
+            """Set the adjustments on the viewport."""
+            self._viewport.set_hadjustment(hAdjustment)
+            self._viewport.set_vadjustment(vAdjustment)
+            self._treeView.set_hadjustment(hAdjustment)
+
+        def _do_size_allocate(self, widget, allocation):
+            """Handler of the size-allocate signal.
+
+            Calls allocate_column_sizes()."""
+            self.allocate_column_sizes(allocation)
+
+        def setStyle(self):
+            """Set the style of the event box from the treeview."""
+            if self._treeView is not None:
+                style = self._treeView.rc_get_style()
+                self._eventBox.modify_bg(0, style.bg[2])
+                self._eventBox.modify_fg(0, style.fg[2])
+
+    class Alignment(gtk.Alignment):
+        """An alignment that remembers the width it was given."""
+        def __init__(self, xalign = 0.0, yalign=0.0,
+                     xscale = 0.0, yscale = 0.0 ):
+            """Construct the alignment."""
+            super(Alignment, self).__init__(xalign = xalign, yalign = yalign,
+                                            xscale = xscale, yscale = yscale)
+            self.allocatedWidth = 0
+            self.connect("size-allocate", self._do_size_allocate)
+
+        def _do_size_allocate(self, widget, allocation):
+            """Called with the new size allocation."""
+            self.allocatedWidth = allocation.width
+
+#------------------------------------------------------------------------------
+
+class CheckButton(gtk.CheckButton):
+    """A check button that contains a reference to a row in the delay code
+    data table."""
+    def __init__(self, delayCodeRow):
+        """Construct the check button."""
+        super(CheckButton, self).__init__()
+        self.delayCodeRow = delayCodeRow
+
+#------------------------------------------------------------------------------
+
+# CAPTION = 1
+
+# DELAYCODE = 2
+
+# _data1 = ( lambda row: row[0].strip(),
+#            ["Num", "Code", "Title", "Description"],
+#            [ (CAPTION, "Others"),
+#              (DELAYCODE, ("        6", "OA     ", "NO GATES/STAND AVAILABLE",
+#                           "Due to own airline activity")),
+#              (DELAYCODE, ("9", "SG", "SCHEDULED GROUND TIME",
+#                           "Planned turnaround time less than declared minimum")),
+#              (CAPTION, "Passenger and baggage"),
+#              (DELAYCODE, ("11", "PD", "LATE CHECK-IN",
+#                           "Check-in reopened for late passengers")),
+#              (DELAYCODE, ("12", "PL", "LATE CHECK-IN",
+#                           "Check-in not completed by flight closure time")),
+#              (DELAYCODE, ("13", "PE", "CHECK-IN ERROR",
+#                           "Error with passenger or baggage details")) ] )
+
+# _data2 = ( lambda row: row[0].strip(),
+#            ["MA", "IATA", "Description"],
+#            [ (CAPTION, "Passenger and baggage"),
+#              (DELAYCODE, (" 012", "01   ",
+#                           "Late shipping of parts and/or materials")),
+#              (DELAYCODE, (" 111", "11",
+#                           "Check-in reopened for late passengers")),
+#              (DELAYCODE, (" 121", "12",
+#                           "Check-in not completed by flight closure time")),
+#              (DELAYCODE, (" 132", "13",
+#                           "Error with passenger or baggage details"))
+#             ])
+
+#------------------------------------------------------------------------------
+
+class DelayCodeTable(DelayCodeTableBase):
+    """The delay code table."""
+    def __init__(self, info):
+        """Construct the delay code table."""
+        super(DelayCodeTable, self).__init__()
+
+        self._info = info
+
+        self._delayCodeData = None
+
+        self._treeView = gtk.TreeView(gtk.ListStore(str, str))
+        self._treeView.set_rules_hint(True)
+
+        self.pack_start(self._treeView, False, False, 0)
+
+        self._alignments = []
+        self._checkButtons = []
+
+        self._eventBox = gtk.EventBox()
+
+        self._table = None
+
+        self._viewport = self._createViewport()
+        self._viewport.add(self._eventBox)
+        self._viewport.set_shadow_type(SHADOW_NONE)
+
+        self.pack_start(self._viewport, True, True, 0)
+
+        self._previousWidth = 0
+
+    @property
+    def delayCodes(self):
+        """Get a list of the delay codes checked by the user."""
+        codes = []
+
+        if self._delayCodeData is not None:
+            codeExtractor = self._delayCodeData[0]
+            for checkButton in self._checkButtons:
+                if checkButton.get_active():
+                    codes.append(codeExtractor(checkButton.delayCodeRow))
+
+        return codes
+
+    @property
+    def hasDelayCode(self):
+        """Determine if there is at least one delay code selected."""
+        if self._delayCodeData is not None:
+            for checkButton in self._checkButtons:
+                if checkButton.get_active():
+                    return True
+
+        return False
+
+    def allocate_column_sizes(self, allocation):
+        """Allocate the column sizes."""
+        if allocation.width!=self._previousWidth:
+            self._previousWidth = allocation.width
+            index = 0
+            lastIndex = len(self._alignments) - 1
+            for alignment in self._alignments:
+                column = self._treeView.get_column(index)
+                width = alignment.allocatedWidth
+                width += 8 if (index==0 or index==lastIndex) else 16
+                column.set_fixed_width(width)
+                index += 1
+
+    def setType(self, aircraftType):
+        """Setup the delay code table according to the given aircraft type."""
+        self._delayCodeData = data = getTable(aircraftType)
+        if data is None:
+            return
+
+        columns = self._treeView.get_columns()
+        for column in columns:
+            self._treeView.remove_column(column)
+
+        (_extractor, headers, rows) = data
+        numColumns = len(headers) + 1
+        numRows = len(rows)
+
+        column = gtk.TreeViewColumn("", gtk.CellRendererText())
+        column.set_sizing(TREE_VIEW_COLUMN_FIXED)
+        self._treeView.append_column(column)
+
+        for header in headers:
+            column = gtk.TreeViewColumn(header, gtk.CellRendererText())
+            column.set_sizing(TREE_VIEW_COLUMN_FIXED)
+            self._treeView.append_column(column)
+
+        self._table = gtk.Table(numRows, numColumns)
+        self._table.set_homogeneous(False)
+        self._table.set_col_spacings(16)
+        self._table.set_row_spacings(4)
+        self._eventBox.add(self._table)
+
+        self._alignments = []
+        self._checkButtons = []
+
+        firstDelayCodeRow = True
+        for i in range(0, numRows):
+            (type, elements) = rows[i]
+            if type==CAPTION:
+                alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                          xscale = 1.0)
+                label = gtk.Label("<b>" + elements + "</b>")
+                label.set_use_markup(True)
+                label.set_alignment(0.0, 0.5)
+                alignment.add(label)
+                self._table.attach(alignment, 1, numColumns, i, i+1,
+                                   yoptions = FILL)
+                self._table.set_row_spacing(i, 8)
+            elif type==DELAYCODE:
+                checkButton = CheckButton(elements)
+                checkButton.connect("toggled", self._delayCodesChanged)
+                self._checkButtons.append(checkButton)
+                alignment = Alignment(xalign = 0.5, yalign = 0.5, xscale = 1.0)
+                alignment.add(checkButton)
+                self._table.attach(alignment, 0, 1, i, i+1,
+                                   xoptions = FILL, yoptions = FILL)
+                if firstDelayCodeRow:
+                    self._alignments.append(alignment)
+
+                for j in range(0, numColumns-1):
+                    label = gtk.Label(elements[j])
+                    label.set_alignment(0.0, 0.5)
+                    alignment = Alignment(xalign = 0.5, yalign = 0.5,
+                                          xscale = 1.0)
+                    alignment.add(label)
+                    xoptions = FILL
+                    if j==(numColumns-2): xoptions |= EXPAND
+                    self._table.attach(alignment, j+1, j+2, i, i+1,
+                                       xoptions = xoptions, yoptions = FILL)
+                    if firstDelayCodeRow:
+                        self._alignments.append(alignment)
+                firstDelayCodeRow = False
+
+        self._previousWidth = 0
+        self.show_all()
+
+    def reset(self):
+        """Reset the delay code table."""
+        columns = self._treeView.get_columns()
+        for column in columns:
+            self._treeView.remove_column(column)
+        self._eventBox.remove(self._table)
+        self._table = None
+        self.show_all()
+
+    def _delayCodesChanged(self, button):
+        """Called when one of the delay codes have changed."""
+        self._info.delayCodesChanged()
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/delaycodes.py
===================================================================
--- src/mlx/gui/delaycodes.py	(revision 555)
+++ 	(revision )
@@ -1,385 +1,0 @@
-# Module to handle the GUI aspects of the table of the delay codes
-
-#------------------------------------------------------------------------------
-
-from dcdata import CAPTION, DELAYCODE, getTable
-
-from mlx.gui.common import *
-
-import mlx.const as const
-
-#------------------------------------------------------------------------------
-
-if pygobject:
-
-#------------------------------------------------------------------------------
-
-    class Viewport(gtk.Viewport):
-        """Viewport implementation that alleviates the problem with improper
-        resizing by the VBox."""
-        def __init__(self):
-            """Construct the viewport."""
-            gtk.Viewport.__init__(self)
-            self._recursive = False
-            self._vboxHeight = None
-
-        def setVBOXHeight(self, vboxHeight):
-            """Set the height of the VBox which will be used to calculate the
-            viewport's height."""
-            self._vboxHeight = vboxHeight
-
-        def do_size_allocate(self, allocation):
-            """Called when the viewport's size is allocated.
-
-            The height in the allocation object is modified so that it is only
-            so high to fit into the VBox."""
-            if self._vboxHeight is not None:
-                allocation.height = self._vboxHeight - allocation.y
-                self._vboxHeight = None
-            gtk.Viewport.do_size_allocate(self, allocation)
-
-    class DelayCodeTableBase(gtk.VBox, gtk.Scrollable):
-        """PyGObject-specific base class for the delay code table."""
-        __gproperties__ = {
-            "vscroll-policy" : ( gtk.ScrollablePolicy,
-                                 "vscroll-policy",
-                                 "The vertical scrolling policy",
-                                 gtk.ScrollablePolicy.MINIMUM,
-                                 gobject.PARAM_READWRITE ),
-            "vadjustment" : ( gtk.Adjustment,
-                              "vadjustment",
-                              "The vertical adjustment",
-                              gobject.PARAM_READWRITE ),
-            "hscroll-policy" : ( gtk.ScrollablePolicy,
-                                 "hscroll-policy",
-                                 "The horizontal scrolling policy",
-                                 gtk.ScrollablePolicy.MINIMUM,
-                                 gobject.PARAM_READWRITE ),
-            "hadjustment" : ( gtk.Adjustment,
-                              "hadjustment",
-                              "The horizontal adjustment",
-                              gobject.PARAM_READWRITE )  }
-
-
-        @staticmethod
-        def _createViewport():
-            """Create an instance of the viewport class used by this base class."""
-            return Viewport()
-
-        def __init__(self):
-            """Construct the delay code table."""
-            super(DelayCodeTableBase, self).__init__()
-
-        def do_size_allocate(self, allocation):
-            """Allocate the size for the table and its children.
-
-            This sets up the VBox height in the viewport and then calls the
-            do_size_allocate() function of VBox()."""
-            self._viewport.setVBOXHeight(allocation.height)
-            gtk.VBox.do_size_allocate(self, allocation)
-            self.allocate_column_sizes(allocation)
-
-        def do_get_property(self, prop):
-            """Get the value of one of the properties defined above.
-
-            The request is forwarded to the viewport."""
-            if prop.name=="vscroll-policy":
-                return self._viewport.get_vscroll_policy()
-            elif prop.name=="hscroll-policy":
-                return self._viewport.get_hscroll_policy()
-            elif prop.name=="vadjustment":
-                return self._viewport.get_vadjustment()
-            elif prop.name=="hadjustment":
-                return self._viewport.get_hadjustment()
-            else:
-                raise AttributeError("mlx.gui.delaycodes.DelayCodeTableBase: property %s is not handled in do_get_property" %
-                                     (prop.name,))
-
-        def do_set_property(self, prop, value):
-            """Set the value of the adjustment properties defined above.
-
-            The adjustments are forwarded to the viewport."""
-            if prop.name=="vadjustment":
-                self._viewport.set_vadjustment(value)
-            elif prop.name=="hadjustment":
-                self._viewport.set_hadjustment(value)
-                self._treeView.set_hadjustment(value)
-            else:
-                raise AttributeError("mlx.gui.delaycodes.DelayCodeTableBase: property %s is not handled in do_set_property" %
-                                     (prop.name,))
-
-        def setStyle(self):
-            """Set the style of the event box from the treeview."""
-
-    class Alignment(gtk.Alignment):
-        """An alignment that remembers the width it was given."""
-        def __init__(self, xalign = 0.0, yalign=0.0,
-                     xscale = 0.0, yscale = 0.0 ):
-            """Construct the alignment."""
-            super(Alignment, self).__init__(xalign = xalign, yalign = yalign,
-                                            xscale = xscale, yscale = yscale)
-            self.allocatedWidth = 0
-
-        def do_size_allocate(self, allocation):
-            """Called with the new size allocation."""
-            self.allocatedWidth = allocation.width
-            gtk.Alignment.do_size_allocate(self, allocation)
-
-#------------------------------------------------------------------------------
-
-else: # pygobject
-
-#------------------------------------------------------------------------------
-
-    class DelayCodeTableBase (gtk.VBox):
-        """Base class of the delay code table for PyGtk."""
-
-        __gsignals__ = {
-            "set-scroll-adjustments": (
-                gobject.SIGNAL_RUN_LAST,
-                gobject.TYPE_NONE, (gtk.Adjustment, gtk.Adjustment))
-                }
-
-        @staticmethod
-        def _createViewport():
-            """Create an instance of the viewport class used by this base class."""
-            return gtk.Viewport()
-
-        def __init__(self):
-            """Construct the base class."""
-            super(DelayCodeTableBase, self).__init__()
-            self.set_set_scroll_adjustments_signal("set-scroll-adjustments")
-            self.connect("size-allocate", self._do_size_allocate)
-
-        def do_set_scroll_adjustments(self, hAdjustment, vAdjustment):
-            """Set the adjustments on the viewport."""
-            self._viewport.set_hadjustment(hAdjustment)
-            self._viewport.set_vadjustment(vAdjustment)
-            self._treeView.set_hadjustment(hAdjustment)
-
-        def _do_size_allocate(self, widget, allocation):
-            """Handler of the size-allocate signal.
-
-            Calls allocate_column_sizes()."""
-            self.allocate_column_sizes(allocation)
-
-        def setStyle(self):
-            """Set the style of the event box from the treeview."""
-            if self._treeView is not None:
-                style = self._treeView.rc_get_style()
-                self._eventBox.modify_bg(0, style.bg[2])
-                self._eventBox.modify_fg(0, style.fg[2])
-
-    class Alignment(gtk.Alignment):
-        """An alignment that remembers the width it was given."""
-        def __init__(self, xalign = 0.0, yalign=0.0,
-                     xscale = 0.0, yscale = 0.0 ):
-            """Construct the alignment."""
-            super(Alignment, self).__init__(xalign = xalign, yalign = yalign,
-                                            xscale = xscale, yscale = yscale)
-            self.allocatedWidth = 0
-            self.connect("size-allocate", self._do_size_allocate)
-
-        def _do_size_allocate(self, widget, allocation):
-            """Called with the new size allocation."""
-            self.allocatedWidth = allocation.width
-
-#------------------------------------------------------------------------------
-
-class CheckButton(gtk.CheckButton):
-    """A check button that contains a reference to a row in the delay code
-    data table."""
-    def __init__(self, delayCodeRow):
-        """Construct the check button."""
-        super(CheckButton, self).__init__()
-        self.delayCodeRow = delayCodeRow
-
-#------------------------------------------------------------------------------
-
-# CAPTION = 1
-
-# DELAYCODE = 2
-
-# _data1 = ( lambda row: row[0].strip(),
-#            ["Num", "Code", "Title", "Description"],
-#            [ (CAPTION, "Others"),
-#              (DELAYCODE, ("        6", "OA     ", "NO GATES/STAND AVAILABLE",
-#                           "Due to own airline activity")),
-#              (DELAYCODE, ("9", "SG", "SCHEDULED GROUND TIME",
-#                           "Planned turnaround time less than declared minimum")),
-#              (CAPTION, "Passenger and baggage"),
-#              (DELAYCODE, ("11", "PD", "LATE CHECK-IN",
-#                           "Check-in reopened for late passengers")),
-#              (DELAYCODE, ("12", "PL", "LATE CHECK-IN",
-#                           "Check-in not completed by flight closure time")),
-#              (DELAYCODE, ("13", "PE", "CHECK-IN ERROR",
-#                           "Error with passenger or baggage details")) ] )
-
-# _data2 = ( lambda row: row[0].strip(),
-#            ["MA", "IATA", "Description"],
-#            [ (CAPTION, "Passenger and baggage"),
-#              (DELAYCODE, (" 012", "01   ",
-#                           "Late shipping of parts and/or materials")),
-#              (DELAYCODE, (" 111", "11",
-#                           "Check-in reopened for late passengers")),
-#              (DELAYCODE, (" 121", "12",
-#                           "Check-in not completed by flight closure time")),
-#              (DELAYCODE, (" 132", "13",
-#                           "Error with passenger or baggage details"))
-#             ])
-
-#------------------------------------------------------------------------------
-
-class DelayCodeTable(DelayCodeTableBase):
-    """The delay code table."""
-    def __init__(self, info):
-        """Construct the delay code table."""
-        super(DelayCodeTable, self).__init__()
-
-        self._info = info
-
-        self._delayCodeData = None
-
-        self._treeView = gtk.TreeView(gtk.ListStore(str, str))
-        self._treeView.set_rules_hint(True)
-
-        self.pack_start(self._treeView, False, False, 0)
-
-        self._alignments = []
-        self._checkButtons = []
-
-        self._eventBox = gtk.EventBox()
-
-        self._table = None
-
-        self._viewport = self._createViewport()
-        self._viewport.add(self._eventBox)
-        self._viewport.set_shadow_type(SHADOW_NONE)
-
-        self.pack_start(self._viewport, True, True, 0)
-
-        self._previousWidth = 0
-
-    @property
-    def delayCodes(self):
-        """Get a list of the delay codes checked by the user."""
-        codes = []
-
-        if self._delayCodeData is not None:
-            codeExtractor = self._delayCodeData[0]
-            for checkButton in self._checkButtons:
-                if checkButton.get_active():
-                    codes.append(codeExtractor(checkButton.delayCodeRow))
-
-        return codes
-
-    @property
-    def hasDelayCode(self):
-        """Determine if there is at least one delay code selected."""
-        if self._delayCodeData is not None:
-            for checkButton in self._checkButtons:
-                if checkButton.get_active():
-                    return True
-
-        return False
-
-    def allocate_column_sizes(self, allocation):
-        """Allocate the column sizes."""
-        if allocation.width!=self._previousWidth:
-            self._previousWidth = allocation.width
-            index = 0
-            lastIndex = len(self._alignments) - 1
-            for alignment in self._alignments:
-                column = self._treeView.get_column(index)
-                width = alignment.allocatedWidth
-                width += 8 if (index==0 or index==lastIndex) else 16
-                column.set_fixed_width(width)
-                index += 1
-
-    def setType(self, aircraftType):
-        """Setup the delay code table according to the given aircraft type."""
-        self._delayCodeData = data = getTable(aircraftType)
-        if data is None:
-            return
-
-        columns = self._treeView.get_columns()
-        for column in columns:
-            self._treeView.remove_column(column)
-
-        (_extractor, headers, rows) = data
-        numColumns = len(headers) + 1
-        numRows = len(rows)
-
-        column = gtk.TreeViewColumn("", gtk.CellRendererText())
-        column.set_sizing(TREE_VIEW_COLUMN_FIXED)
-        self._treeView.append_column(column)
-
-        for header in headers:
-            column = gtk.TreeViewColumn(header, gtk.CellRendererText())
-            column.set_sizing(TREE_VIEW_COLUMN_FIXED)
-            self._treeView.append_column(column)
-
-        self._table = gtk.Table(numRows, numColumns)
-        self._table.set_homogeneous(False)
-        self._table.set_col_spacings(16)
-        self._table.set_row_spacings(4)
-        self._eventBox.add(self._table)
-
-        self._alignments = []
-        self._checkButtons = []
-
-        firstDelayCodeRow = True
-        for i in range(0, numRows):
-            (type, elements) = rows[i]
-            if type==CAPTION:
-                alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                          xscale = 1.0)
-                label = gtk.Label("<b>" + elements + "</b>")
-                label.set_use_markup(True)
-                label.set_alignment(0.0, 0.5)
-                alignment.add(label)
-                self._table.attach(alignment, 1, numColumns, i, i+1,
-                                   yoptions = FILL)
-                self._table.set_row_spacing(i, 8)
-            elif type==DELAYCODE:
-                checkButton = CheckButton(elements)
-                checkButton.connect("toggled", self._delayCodesChanged)
-                self._checkButtons.append(checkButton)
-                alignment = Alignment(xalign = 0.5, yalign = 0.5, xscale = 1.0)
-                alignment.add(checkButton)
-                self._table.attach(alignment, 0, 1, i, i+1,
-                                   xoptions = FILL, yoptions = FILL)
-                if firstDelayCodeRow:
-                    self._alignments.append(alignment)
-
-                for j in range(0, numColumns-1):
-                    label = gtk.Label(elements[j])
-                    label.set_alignment(0.0, 0.5)
-                    alignment = Alignment(xalign = 0.5, yalign = 0.5,
-                                          xscale = 1.0)
-                    alignment.add(label)
-                    xoptions = FILL
-                    if j==(numColumns-2): xoptions |= EXPAND
-                    self._table.attach(alignment, j+1, j+2, i, i+1,
-                                       xoptions = xoptions, yoptions = FILL)
-                    if firstDelayCodeRow:
-                        self._alignments.append(alignment)
-                firstDelayCodeRow = False
-
-        self._previousWidth = 0
-        self.show_all()
-
-    def reset(self):
-        """Reset the delay code table."""
-        columns = self._treeView.get_columns()
-        for column in columns:
-            self._treeView.remove_column(column)
-        self._eventBox.remove(self._table)
-        self._table = None
-        self.show_all()
-
-    def _delayCodesChanged(self, button):
-        """Called when one of the delay codes have changed."""
-        self._info.delayCodesChanged()
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/faultexplain.py
===================================================================
--- src/mlx/gui/faultexplain.py	(revision 620)
+++ src/mlx/gui/faultexplain.py	(revision 620)
@@ -0,0 +1,306 @@
+# Module to provide a widget displaying faults that occured during the flight
+# and giving the user some space to explain it
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.faultexplain
+#
+# The widget and associated logic to display faults and allow the pilot to
+# explain them. Each fault is displayed as the text it is accompanied by in the
+# log and there is a text entry field where the user can enter the
+# corresponding explanation. \ref FaultFrame belongs to one fault, while
+# \ref FaultExplainWidget contains the collection of all frames, which is a
+# VBox in a scrolled window.
+
+#-------------------------------------------------------------------------------
+
+class FaultFrame(gtk.Frame):
+    """A frame containing the information about a single fault.
+
+    It consists of a text view with the text of the fault and an editable text
+    view for the explanation."""
+    def __init__(self, faultText):
+        """Construct the frame."""
+        gtk.Frame.__init__(self)
+
+        self._faultText = faultText
+
+        vbox = gtk.VBox()
+
+        self._fault = fault = gtk.TextView()
+        fault.set_editable(False)
+        fault.set_can_focus(False)
+        fault.set_wrap_mode(WRAP_WORD)
+
+        buffer = fault.get_buffer()
+        self._faultTag  = buffer.create_tag("fault", weight=WEIGHT_BOLD)
+
+        self.faultText = faultText
+
+        faultAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                       xscale = 1.0, yscale = 0.0)
+        faultAlignment.set_padding(padding_top = 0, padding_bottom = 0,
+                                   padding_left = 2, padding_right = 2)
+        faultAlignment.add(fault)
+        vbox.pack_start(faultAlignment, True, True, 4)
+
+        self._explanation = explanation = gtk.TextView()
+        explanation.set_wrap_mode(WRAP_WORD)
+        explanation.set_accepts_tab(False)
+        explanation.set_size_request(-1, 100)
+
+        buffer = explanation.get_buffer()
+        buffer.connect("changed", self._explanationChanged)
+
+        vbox.pack_start(explanation, True, True, 4)
+
+        self.add(vbox)
+        self.show_all()
+
+        if pygobject:
+            styleContext = self.get_style_context()
+            color = styleContext.get_background_color(gtk.StateFlags.NORMAL)
+            fault.override_background_color(0, color)
+        else:
+            style = self.rc_get_style()
+            fault.modify_base(0, style.bg[0])
+
+        self._hasExplanation = False
+
+    @property
+    def faultText(self):
+        """Get the text of the fault."""
+        return self._faultText
+
+    @faultText.setter
+    def faultText(self, faultText):
+        """Update the text of the fault."""
+        self._faultText = faultText
+
+        buffer = self._fault.get_buffer()
+        buffer.set_text(faultText)
+        buffer.apply_tag(self._faultTag,
+                         buffer.get_start_iter(), buffer.get_end_iter())
+
+    @property
+    def explanation(self):
+        """Get the text of the explanation."""
+        buffer = self._explanation.get_buffer()
+        return buffer.get_text(buffer.get_start_iter(),
+                               buffer.get_end_iter(), True)
+
+    @property
+    def hasExplanation(self):
+        """Determine if there is a valid explanation."""
+        return self._hasExplanation
+
+    @property
+    def html(self):
+        """Convert the contents of the widget into HTML."""
+        return self._faultText + "<br/></b>" + self.explanation + "<b>"
+
+    def setMnemonicFor(self, widget):
+        """Set the explanation text view as the mnemonic widget for the given
+        one."""
+        widget.set_mnemonic_widget(self._explanation)
+
+    def _explanationChanged(self, textBuffer):
+        """Called when the explanation's text has changed."""
+        hasExplanation = len(textBuffer.get_text(textBuffer.get_start_iter(),
+                                                 textBuffer.get_end_iter(),
+                                                 True))>3
+        if self._hasExplanation != hasExplanation:
+            self._hasExplanation = hasExplanation
+            self.emit("explanation-changed", hasExplanation)
+
+#-------------------------------------------------------------------------------
+
+gobject.signal_new("explanation-changed", FaultFrame, gobject.SIGNAL_RUN_FIRST,
+                   None, (bool,))
+
+#-------------------------------------------------------------------------------
+
+class FaultExplainWidget(gtk.Frame):
+    """The widget for the failts and their explanations."""
+    @staticmethod
+    def getFaultFrame(alignment):
+        """Get the fault frame from the given alignment."""
+        return alignment.get_children()[0]
+
+    def __init__(self, gui):
+        gtk.Frame.__init__(self)
+
+        self._gui = gui
+        self.set_label(xstr("info_faults"))
+        label = self.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 = 4, padding_right = 4)
+
+        self._outerBox = outerBox = gtk.EventBox()
+        outerBox.add(alignment)
+
+        self._innerBox = innerBox = gtk.EventBox()
+        alignment.add(self._innerBox)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 0)
+
+        innerBox.add(alignment)
+
+        scroller = gtk.ScrolledWindow()
+        scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scroller.set_shadow_type(SHADOW_NONE)
+
+        self._faults = gtk.VBox()
+        self._faults.set_homogeneous(False)
+        scroller.add_with_viewport(self._faults)
+
+        alignment.add(scroller)
+
+        self._faultWidgets = {}
+
+        self.add(outerBox)
+        self.show_all()
+
+        self._numFaults = 0
+        self._numExplanations = 0
+
+    @property
+    def fullyExplained(self):
+        """Indicate if the faults have been fully explained."""
+        return self._numExplanations>=self._numFaults
+
+    @property
+    def html(self):
+        """Convert the contents of the widget into HTML."""
+        html = ""
+        for alignment in self._faults.get_children():
+            faultFrame = FaultExplainWidget.getFaultFrame(alignment)
+            if html:
+                html += "<br><br>"
+            html += faultFrame.html
+        return html
+
+    def addFault(self, id, faultText):
+        """Add a fault with the given ID and text."""
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                      xscale = 1.0, yscale = 0.0)
+        alignment.set_padding(padding_top = 2, padding_bottom = 2,
+                              padding_left = 4, padding_right = 4)
+
+        faultFrame = FaultFrame(faultText)
+        if self._numFaults==0:
+            faultFrame.setMnemonicFor(self.get_label_widget())
+        faultFrame.connect("explanation-changed", self._explanationChanged)
+
+        alignment.add(faultFrame)
+        self._faults.pack_start(alignment, False, False, 4)
+        self._faults.show_all()
+
+        self._faultWidgets[id] = (alignment, faultFrame)
+
+        self._updateStats(numFaults = self._numFaults + 1)
+
+    def updateFault(self, id, faultText):
+        """Update the text of the fault with the given ID."""
+        self._faultWidgets[id][1].faultText = faultText
+
+    def clearFault(self, id):
+        """Clear (remove) the fault with the given ID."""
+        (alignment, faultFrame) = self._faultWidgets[id]
+        hasExplanation = faultFrame.hasExplanation
+
+        children = self._faults.get_children()
+        if alignment is children[0] and len(children)>1:
+            faultFrame = FaultExplainWidget.getFaultFrame(children[1])
+            faultFrame.setMnemonicFor(self.get_label_widget())
+
+        self._faults.remove(alignment)
+        self._faults.show_all()
+
+        del self._faultWidgets[id]
+
+        self._updateStats(numFaults = self._numFaults - 1,
+                          numExplanations = self._numExplanations -
+                          (1 if hasExplanation else 0))
+
+    def reset(self):
+        """Reset the widget by removing all faults."""
+        for (alignment, faultFrame) in self._faultWidgets.itervalues():
+            self._faults.remove(alignment)
+        self._faults.show_all()
+
+        self._faultWidgets = {}
+        self._numFaults = self._numExplanations = 0
+        self._setColor()
+
+    def set_sensitive(self, sensitive):
+        """Set the sensitiviy of the widget.
+
+        The outer event box's sensitivity is changed only."""
+        self._outerBox.set_sensitive(sensitive)
+
+    def _updateStats(self, numFaults = None, numExplanations = None):
+        """Update the statistics.
+
+        If the explanation status has changed, emit the corresponding
+        signal."""
+        before = self.fullyExplained
+
+        if numFaults is None:
+            numFaults = self._numFaults
+
+        if numExplanations is None:
+            numExplanations = self._numExplanations
+
+        after = numExplanations >= numFaults
+
+        self._numFaults = numFaults
+        self._numExplanations = numExplanations
+
+        if before!=after:
+            self._setColor()
+            self.emit("explanations-changed", after)
+
+    def _explanationChanged(self, faultFrame, hasExplanation):
+        """Called when the status of an explanation has changed."""
+        self._updateStats(numExplanations = (self._numExplanations +
+                                             (1 if hasExplanation else -1)))
+
+    def _setColor(self):
+        """Set the color to indicate if an unexplained fault is present or
+        not."""
+        allExplained = self._numExplanations >= self._numFaults
+        if pygobject:
+            styleContext = self.get_style_context()
+            if allExplained:
+                outerColour = innerColour = gdk.RGBA(red = 0.0, green=0.0,
+                                                     blue=0.0, alpha=0.0)
+            else:
+                outerColour = \
+                  styleContext.get_background_color(gtk.StateFlags.SELECTED)
+                innerColour = self._gui.backgroundColour
+
+            self._outerBox.override_background_color(gtk.StateFlags.NORMAL,
+                                                     outerColour)
+            self._innerBox.override_background_color(gtk.StateFlags.NORMAL,
+                                                     innerColour)
+        else:
+            style = self.rc_get_style()
+            self._outerBox.modify_bg(0, style.bg[0 if allExplained else 3])
+
+#-------------------------------------------------------------------------------
+
+gobject.signal_new("explanations-changed", FaultExplainWidget,
+                   gobject.SIGNAL_RUN_FIRST, None, (bool,))
Index: src/mlx/gui/faultexplain.py
===================================================================
--- src/mlx/gui/faultexplain.py	(revision 620)
+++ 	(revision )
@@ -1,306 +1,0 @@
-# Module to provide a widget displaying faults that occured during the flight
-# and giving the user some space to explain it
-
-#-------------------------------------------------------------------------------
-
-from mlx.gui.common import *
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.faultexplain
-#
-# The widget and associated logic to display faults and allow the pilot to
-# explain them. Each fault is displayed as the text it is accompanied by in the
-# log and there is a text entry field where the user can enter the
-# corresponding explanation. \ref FaultFrame belongs to one fault, while
-# \ref FaultExplainWidget contains the collection of all frames, which is a
-# VBox in a scrolled window.
-
-#-------------------------------------------------------------------------------
-
-class FaultFrame(gtk.Frame):
-    """A frame containing the information about a single fault.
-
-    It consists of a text view with the text of the fault and an editable text
-    view for the explanation."""
-    def __init__(self, faultText):
-        """Construct the frame."""
-        gtk.Frame.__init__(self)
-
-        self._faultText = faultText
-
-        vbox = gtk.VBox()
-
-        self._fault = fault = gtk.TextView()
-        fault.set_editable(False)
-        fault.set_can_focus(False)
-        fault.set_wrap_mode(WRAP_WORD)
-
-        buffer = fault.get_buffer()
-        self._faultTag  = buffer.create_tag("fault", weight=WEIGHT_BOLD)
-
-        self.faultText = faultText
-
-        faultAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                       xscale = 1.0, yscale = 0.0)
-        faultAlignment.set_padding(padding_top = 0, padding_bottom = 0,
-                                   padding_left = 2, padding_right = 2)
-        faultAlignment.add(fault)
-        vbox.pack_start(faultAlignment, True, True, 4)
-
-        self._explanation = explanation = gtk.TextView()
-        explanation.set_wrap_mode(WRAP_WORD)
-        explanation.set_accepts_tab(False)
-        explanation.set_size_request(-1, 100)
-
-        buffer = explanation.get_buffer()
-        buffer.connect("changed", self._explanationChanged)
-
-        vbox.pack_start(explanation, True, True, 4)
-
-        self.add(vbox)
-        self.show_all()
-
-        if pygobject:
-            styleContext = self.get_style_context()
-            color = styleContext.get_background_color(gtk.StateFlags.NORMAL)
-            fault.override_background_color(0, color)
-        else:
-            style = self.rc_get_style()
-            fault.modify_base(0, style.bg[0])
-
-        self._hasExplanation = False
-
-    @property
-    def faultText(self):
-        """Get the text of the fault."""
-        return self._faultText
-
-    @faultText.setter
-    def faultText(self, faultText):
-        """Update the text of the fault."""
-        self._faultText = faultText
-
-        buffer = self._fault.get_buffer()
-        buffer.set_text(faultText)
-        buffer.apply_tag(self._faultTag,
-                         buffer.get_start_iter(), buffer.get_end_iter())
-
-    @property
-    def explanation(self):
-        """Get the text of the explanation."""
-        buffer = self._explanation.get_buffer()
-        return buffer.get_text(buffer.get_start_iter(),
-                               buffer.get_end_iter(), True)
-
-    @property
-    def hasExplanation(self):
-        """Determine if there is a valid explanation."""
-        return self._hasExplanation
-
-    @property
-    def html(self):
-        """Convert the contents of the widget into HTML."""
-        return self._faultText + "<br/></b>" + self.explanation + "<b>"
-
-    def setMnemonicFor(self, widget):
-        """Set the explanation text view as the mnemonic widget for the given
-        one."""
-        widget.set_mnemonic_widget(self._explanation)
-
-    def _explanationChanged(self, textBuffer):
-        """Called when the explanation's text has changed."""
-        hasExplanation = len(textBuffer.get_text(textBuffer.get_start_iter(),
-                                                 textBuffer.get_end_iter(),
-                                                 True))>3
-        if self._hasExplanation != hasExplanation:
-            self._hasExplanation = hasExplanation
-            self.emit("explanation-changed", hasExplanation)
-
-#-------------------------------------------------------------------------------
-
-gobject.signal_new("explanation-changed", FaultFrame, gobject.SIGNAL_RUN_FIRST,
-                   None, (bool,))
-
-#-------------------------------------------------------------------------------
-
-class FaultExplainWidget(gtk.Frame):
-    """The widget for the failts and their explanations."""
-    @staticmethod
-    def getFaultFrame(alignment):
-        """Get the fault frame from the given alignment."""
-        return alignment.get_children()[0]
-
-    def __init__(self, gui):
-        gtk.Frame.__init__(self)
-
-        self._gui = gui
-        self.set_label(xstr("info_faults"))
-        label = self.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 = 4, padding_right = 4)
-
-        self._outerBox = outerBox = gtk.EventBox()
-        outerBox.add(alignment)
-
-        self._innerBox = innerBox = gtk.EventBox()
-        alignment.add(self._innerBox)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 0)
-
-        innerBox.add(alignment)
-
-        scroller = gtk.ScrolledWindow()
-        scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scroller.set_shadow_type(SHADOW_NONE)
-
-        self._faults = gtk.VBox()
-        self._faults.set_homogeneous(False)
-        scroller.add_with_viewport(self._faults)
-
-        alignment.add(scroller)
-
-        self._faultWidgets = {}
-
-        self.add(outerBox)
-        self.show_all()
-
-        self._numFaults = 0
-        self._numExplanations = 0
-
-    @property
-    def fullyExplained(self):
-        """Indicate if the faults have been fully explained."""
-        return self._numExplanations>=self._numFaults
-
-    @property
-    def html(self):
-        """Convert the contents of the widget into HTML."""
-        html = ""
-        for alignment in self._faults.get_children():
-            faultFrame = FaultExplainWidget.getFaultFrame(alignment)
-            if html:
-                html += "<br><br>"
-            html += faultFrame.html
-        return html
-
-    def addFault(self, id, faultText):
-        """Add a fault with the given ID and text."""
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                      xscale = 1.0, yscale = 0.0)
-        alignment.set_padding(padding_top = 2, padding_bottom = 2,
-                              padding_left = 4, padding_right = 4)
-
-        faultFrame = FaultFrame(faultText)
-        if self._numFaults==0:
-            faultFrame.setMnemonicFor(self.get_label_widget())
-        faultFrame.connect("explanation-changed", self._explanationChanged)
-
-        alignment.add(faultFrame)
-        self._faults.pack_start(alignment, False, False, 4)
-        self._faults.show_all()
-
-        self._faultWidgets[id] = (alignment, faultFrame)
-
-        self._updateStats(numFaults = self._numFaults + 1)
-
-    def updateFault(self, id, faultText):
-        """Update the text of the fault with the given ID."""
-        self._faultWidgets[id][1].faultText = faultText
-
-    def clearFault(self, id):
-        """Clear (remove) the fault with the given ID."""
-        (alignment, faultFrame) = self._faultWidgets[id]
-        hasExplanation = faultFrame.hasExplanation
-
-        children = self._faults.get_children()
-        if alignment is children[0] and len(children)>1:
-            faultFrame = FaultExplainWidget.getFaultFrame(children[1])
-            faultFrame.setMnemonicFor(self.get_label_widget())
-
-        self._faults.remove(alignment)
-        self._faults.show_all()
-
-        del self._faultWidgets[id]
-
-        self._updateStats(numFaults = self._numFaults - 1,
-                          numExplanations = self._numExplanations -
-                          (1 if hasExplanation else 0))
-
-    def reset(self):
-        """Reset the widget by removing all faults."""
-        for (alignment, faultFrame) in self._faultWidgets.itervalues():
-            self._faults.remove(alignment)
-        self._faults.show_all()
-
-        self._faultWidgets = {}
-        self._numFaults = self._numExplanations = 0
-        self._setColor()
-
-    def set_sensitive(self, sensitive):
-        """Set the sensitiviy of the widget.
-
-        The outer event box's sensitivity is changed only."""
-        self._outerBox.set_sensitive(sensitive)
-
-    def _updateStats(self, numFaults = None, numExplanations = None):
-        """Update the statistics.
-
-        If the explanation status has changed, emit the corresponding
-        signal."""
-        before = self.fullyExplained
-
-        if numFaults is None:
-            numFaults = self._numFaults
-
-        if numExplanations is None:
-            numExplanations = self._numExplanations
-
-        after = numExplanations >= numFaults
-
-        self._numFaults = numFaults
-        self._numExplanations = numExplanations
-
-        if before!=after:
-            self._setColor()
-            self.emit("explanations-changed", after)
-
-    def _explanationChanged(self, faultFrame, hasExplanation):
-        """Called when the status of an explanation has changed."""
-        self._updateStats(numExplanations = (self._numExplanations +
-                                             (1 if hasExplanation else -1)))
-
-    def _setColor(self):
-        """Set the color to indicate if an unexplained fault is present or
-        not."""
-        allExplained = self._numExplanations >= self._numFaults
-        if pygobject:
-            styleContext = self.get_style_context()
-            if allExplained:
-                outerColour = innerColour = gdk.RGBA(red = 0.0, green=0.0,
-                                                     blue=0.0, alpha=0.0)
-            else:
-                outerColour = \
-                  styleContext.get_background_color(gtk.StateFlags.SELECTED)
-                innerColour = self._gui.backgroundColour
-
-            self._outerBox.override_background_color(gtk.StateFlags.NORMAL,
-                                                     outerColour)
-            self._innerBox.override_background_color(gtk.StateFlags.NORMAL,
-                                                     innerColour)
-        else:
-            style = self.rc_get_style()
-            self._outerBox.modify_bg(0, style.bg[0 if allExplained else 3])
-
-#-------------------------------------------------------------------------------
-
-gobject.signal_new("explanations-changed", FaultExplainWidget,
-                   gobject.SIGNAL_RUN_FIRST, None, (bool,))
Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 690)
+++ src/mlx/gui/flight.py	(revision 690)
@@ -0,0 +1,4395 @@
+
+from mlx.gui.common import *
+import mlx.gui.cef as cef
+
+import mlx.const as const
+import mlx.fs as fs
+import mlx.acft as acft
+from mlx.flight import Flight
+from mlx.checks import PayloadChecker
+from mlx.gates import lhbpGates
+import mlx.util as util
+from mlx.pirep import PIREP
+from mlx.i18n import xstr
+from mlx.sound import startSound
+import mlx.web as web
+
+import datetime
+import time
+import os
+import tempfile
+
+#-----------------------------------------------------------------------------
+
+## @package mlx.gui.flight
+#
+# The flight "wizard".
+#
+# This module implements the main tab of the application, the flight
+# wizard. The wizard consists of \ref Page "pages", that come one after the
+# other. As some pages might be skipped, the pages dynamically store the index
+# of the previous page so that going back to it is simpler. The \ref
+# Page.activate "activate" function is called before a page is first shown
+# during a flight. This function should initialize the page's controls and fill
+# it with initial data. When a page is left for the first time, its \ref
+# Page.finalize "finalize" function is called. It should set those controls
+# insensitive, that will not be available if the user comes back to this page.
+#
+# Each page has a title at the top displayed in inverted colors and a big
+# font. There is a help text below it centered, that shortly describes what is
+# expected on the page. There can be two help texts: one shown when the page is
+# first displayed during a flight, another shown when the user goes back to the
+# page. The main content area is below this, also centered. Finally, there are
+# some buttons at the bottom on the right. As some buttons occur very
+# frequently, there are functions to add them (\ref Page.addCancelFlightButton
+# "addCancelFlightButton", \ref Page.addPreviousButton "addPreviousButton" and
+# \ref Page.addNextButton "addNextButton".
+#
+# The \ref Wizard class is the main class to collect the pages. It also stores
+# some data passed from one page to another and provides properties to access
+# data items set via the wizard pages.
+
+#-----------------------------------------------------------------------------
+
+comboModel = gtk.ListStore(gobject.TYPE_STRING)
+comboModel.append(("N/A",))
+comboModel.append(("VECTORS",))
+
+#-----------------------------------------------------------------------------
+
+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)
+
+        self._vbox = gtk.VBox()
+        self._vbox.set_homogeneous(False)
+        frame.add(self._vbox)
+
+        eventBox = gtk.EventBox()
+
+        alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
+
+        titleLabel = gtk.Label(title)
+        titleLabel.modify_font(pango.FontDescription("bold 24"))
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 6, padding_right = 0)
+
+        alignment.add(titleLabel)
+        eventBox.add(alignment)
+
+        self._vbox.pack_start(eventBox, False, False, 0)
+
+        self._titleEventBox = eventBox
+        self._titleLabel = titleLabel
+
+        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(longerHelp)
+        # 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._cancelFlightButton = None
+
+        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, padding = 4):
+        """Add a button with the given label.
+
+        Return the button object created."""
+        button = gtk.Button(label)
+        self._buttonBox.pack_start(button, False, False, padding)
+        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 addCancelFlightButton(self):
+        """Add the 'Cancel flight' button to the page."""
+        self._cancelFlightButton = \
+            self.addButton(xstr("button_cancelFlight"),
+                           sensitive = True,
+                           tooltip = xstr("button_cancelFlight_tooltip"),
+                           clicked = self._cancelFlight,
+                           padding = 16)
+        return self._cancelFlightButton
+
+    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 setStyle(self):
+        """Set the styles of some of the items on the page."""
+        if pygobject:
+            context = self.get_style_context()
+            color = context.get_background_color(gtk.StateFlags.SELECTED)
+            self._titleEventBox.modify_bg(0, color.to_color())
+            color = context.get_color(gtk.StateFlags.SELECTED)
+            self._titleLabel.modify_fg(0, color.to_color())
+        else:
+            style = self.rc_get_style()
+            self._titleEventBox.modify_bg(0, style.bg[3])
+            self._titleLabel.modify_fg(0, style.fg[3])
+
+    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 setHelp(self, help):
+        """Set the help string."""
+        self._help = help
+        if not self._completed:
+            self._helpLabel.set_markup(self._help)
+            self._helpLabel.set_sensitive(True)
+
+    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
+        if self._cancelFlightButton is not None:
+            self._cancelFlightButton.set_sensitive(True)
+
+    def goBack(self):
+        """Go to the page we were invoked from."""
+        assert self._fromPage is not None
+
+        self._wizard.setCurrentPage(self._fromPage, finalize = False)
+
+    def flightEnded(self):
+        """Called when the flight has ended.
+
+        This default implementation disables the cancel flight button."""
+        if self._cancelFlightButton is not None:
+            self._cancelFlightButton.set_sensitive(False)
+
+    def _cancelFlight(self, button):
+        """Called when the Cancel flight button is clicked."""
+        self._wizard.gui.cancelFlight()
+
+#-----------------------------------------------------------------------------
+
+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(4, 2)
+        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._pilotIDChanged)
+        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._setControls)
+        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._entranceExam = gtk.CheckButton(xstr("login_entranceExam"))
+        self._entranceExam.set_use_underline(True)
+        self._entranceExam.set_tooltip_text(xstr("login_entranceExam_tooltip"))
+        self._entranceExam.connect("toggled", self._setControls)
+        table.attach(self._entranceExam, 1, 2, 3, 4, ypadding = 12)
+
+        self.addButton(xstr("button_offline"),
+                       clicked = self._offlineClicked,
+                       tooltip = xstr("button_offline_tooltip"))
+
+        self._loginButton = self.addButton(xstr("button_login"), default = True)
+        self._loginButton.connect("clicked", self._loginClicked)
+        self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))
+
+
+    @property
+    def entranceExam(self):
+        """Get whether an entrance exam is being performed."""
+        return self._entranceExam.get_active() and \
+               self._pilotID.get_text()!=""
+
+    @property
+    def pilotID(self):
+        """Get the pilot ID, if given."""
+        return self._pilotID.get_text()
+
+    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)
+        self._setControls(None)
+
+    def _pilotIDChanged(self, entry):
+        """Called when the pilot ID has changed.
+
+        It sets the text to upper-case and calls _setControls to update other
+        stuff."""
+        entry.set_text(entry.get_text().upper())
+        self._setControls(entry)
+
+    def _setControls(self, entry = None):
+        """Set the sensitivity of the various controls.
+
+        The login button is sensitive only if both the pilot ID and the
+        password fields contain values.
+
+        The password field is sensitive only, if the entrance exam checkbox is
+        not selected.
+
+        The remember password checkbox is sensitive only, if the password field
+        contains text.
+
+        The entrance exam checkbox is sensitive only, if the pilot ID is not
+        empty."""
+        pilotID = self._pilotID.get_text()
+        password = self._password.get_text()
+        entranceExam = self._entranceExam.get_active()
+        self._password.set_sensitive(not entranceExam)
+        self._rememberButton.set_sensitive(password!="" and not entranceExam)
+        self._entranceExam.set_sensitive(pilotID!="")
+        self._loginButton.set_sensitive(pilotID!="" and
+                                        (password!="" or entranceExam))
+
+    def _offlineClicked(self, button):
+        """Called when the offline button was clicked."""
+        print "mlx.flight.LoginPage: offline flight selected"
+        self._wizard.nextPage()
+
+    def _loginClicked(self, button):
+        """Called when the login button was clicked."""
+        print "mlx.flight.LoginPage: logging in"
+        self._wizard.login(self._handleLoginResult,
+                           self._pilotID.get_text(),
+                           self._password.get_text(),
+                           self.entranceExam)
+
+    def _handleLoginResult(self, returned, result):
+        """Handle the login result."""
+        self._loginButton.set_sensitive(True)
+        if returned and result.loggedIn:
+            config = self._wizard.gui.config
+
+            config.pilotID = self._pilotID.get_text()
+
+            rememberPassword = self._rememberButton.get_active()
+            config.password = result.password if rememberPassword else ""
+
+            config.rememberPassword = rememberPassword
+
+            config.save()
+            self._wizard.nextPage()
+
+#-----------------------------------------------------------------------------
+
+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)
+        self._flightList.connect("row-activated", self._rowActivated)
+        self._flightList.connect("button-press-event", self._listButtonPressed)
+
+        self._flightListPopupMenu = None
+
+        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._saveButton = self.addButton(xstr("flightsel_save"),
+                                          sensitive = False,
+                                          clicked = self._saveClicked,
+                                          tooltip = xstr("flightsel_save_tooltip"))
+        self._saveDialog = None
+
+        self._refreshButton = self.addButton(xstr("flightsel_refresh"),
+                                             sensitive = True,
+                                             clicked = self._refreshClicked,
+                                             tooltip = xstr("flightsel_refresh_tooltip"))
+
+        self._loadButton = self.addButton(xstr("flightsel_load"),
+                                          sensitive = True,
+                                          tooltip = xstr("flightsel_load_tooltip"))
+        self._loadButton.connect("clicked", self._loadButtonClicked)
+        self._loadDialog = None
+
+        self._button = self.addNextButton(sensitive = False,
+                                          clicked =  self._forwardClicked)
+
+        self._flights = []
+
+    def activate(self):
+        """Fill the flight list."""
+        self._flightList.set_sensitive(True)
+        self._loadButton.set_sensitive(True)
+        self._refreshButton.set_sensitive(self._wizard.loggedIn)
+        self._buildFlights()
+
+    def finalize(self):
+        """Finalize the page."""
+        self._flightList.set_sensitive(False)
+        self._loadButton.set_sensitive(False)
+        self._refreshButton.set_sensitive(False)
+
+    def _buildFlights(self):
+        """Rebuild the flights from the login result."""
+        self._flights = []
+        self._listStore.clear()
+        if self._wizard.loggedIn:
+            for flight in self._wizard.loginResult.flights:
+                self._addFlight(flight)
+
+    def _addFlight(self, flight):
+        """Add the given file to the list of flights."""
+        self._flights.append(flight)
+        self._listStore.append([str(flight.departureTime),
+                                flight.callsign,
+                                flight.departureICAO,
+                                flight.arrivalICAO])
+
+    def _saveClicked(self, button):
+        """Called when the Save flight button is clicked."""
+        self._saveSelected()
+
+    def _saveSelected(self):
+        """Save the selected flight."""
+        flight = self._getSelectedFlight()
+        date = flight.departureTime.date()
+        name = "%04d-%02d-%02d %s %s-%s.vaflight" % \
+               (date.year, date.month, date.day, flight.callsign,
+                flight.departureICAO, flight.arrivalICAO)
+
+        dialog = self._getSaveDialog()
+        dialog.set_current_name(name)
+        dialog.show_all()
+        response = dialog.run()
+        dialog.hide()
+
+        if response==RESPONSETYPE_OK:
+            fileName = text2unicode(dialog.get_filename())
+            print "Saving", fileName
+            try:
+                with open(fileName, "wt") as f:
+                    flight.writeIntoFile(f)
+            except Exception, e:
+                print "Failed to save flight:", util.utf2unicode(str(e))
+                dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
+                                           type = MESSAGETYPE_ERROR,
+                                           message_format =
+                                           xstr("flightsel_save_failed"))
+                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+                dialog.set_title(WINDOW_TITLE_BASE)
+                secondary = xstr("flightsel_save_failed_sec")
+                dialog.format_secondary_markup(secondary)
+                dialog.run()
+                dialog.hide()
+
+    def _refreshClicked(self, button):
+        """Called when the refresh button is clicked."""
+        self._wizard.reloadFlights(self._refreshCallback)
+
+    def _refreshCallback(self, returned, result):
+        """Callback for the refresh."""
+        if returned and result.loggedIn:
+            self._buildFlights()
+
+    def _selectionChanged(self, selection):
+        """Called when the selection is changed."""
+        selected = selection.count_selected_rows()==1
+        self._saveButton.set_sensitive(selected)
+        self._button.set_sensitive(selected)
+
+    def _loadButtonClicked(self, loadButton):
+        """Called when the load a flight button is clicked."""
+        dialog = self._getLoadDialog()
+        dialog.show_all()
+        response = dialog.run()
+        dialog.hide()
+
+        if response==RESPONSETYPE_OK:
+            fileName = text2unicode(dialog.get_filename())
+            print "Loading", fileName
+            bookedFlight = web.BookedFlight()
+            try:
+                with open(fileName, "rt") as f:
+                    bookedFlight.readFromFile(f)
+                self._addFlight(bookedFlight)
+            except Exception, e:
+                print "Failed to load flight:", util.utf2unicode(str(e))
+                dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
+                                           type = MESSAGETYPE_ERROR,
+                                           message_format =
+                                           xstr("flightsel_load_failed"))
+                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+                dialog.set_title(WINDOW_TITLE_BASE)
+                secondary = xstr("flightsel_load_failed_sec")
+                dialog.format_secondary_markup(secondary)
+                dialog.run()
+                dialog.hide()
+
+    def _forwardClicked(self, button):
+        """Called when the forward button was clicked."""
+        if self._completed:
+            self._wizard.jumpPage(self._nextDistance, finalize = False)
+        else:
+            self._flightSelected()
+
+    def _rowActivated(self, flightList, path, column):
+        """Called when a row is activated."""
+        if not self._completed:
+            self._flightSelected()
+
+    def _flightSelected(self):
+        """Called when a flight has been selected."""
+        flight = self._getSelectedFlight()
+        self._wizard._bookedFlight = flight
+        self._wizard.gui.enableFlightInfo(flight.aircraftType)
+
+        self._updateDepartureGate()
+
+    def _getSelectedFlight(self):
+        """Get the currently selected flight."""
+        selection = self._flightList.get_selection()
+        (listStore, iter) = selection.get_selected()
+        path = listStore.get_path(iter)
+        [index] = path.get_indices() if pygobject else path
+
+        return self._flights[index]
+
+    def _listButtonPressed(self, widget, event):
+        """Called when a mouse button is pressed on the flight list."""
+        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
+            return
+
+        (path, _, _, _) = self._flightList.get_path_at_pos(int(event.x),
+                                                           int(event.y))
+        selection = self._flightList.get_selection()
+        selection.unselect_all()
+        selection.select_path(path)
+
+        menu = self._getListPopupMenu()
+        if pygobject:
+            menu.popup(None, None, None, None, event.button, event.time)
+        else:
+            menu.popup(None, None, None, event.button, event.time)
+
+    def _updateDepartureGate(self):
+        """Update the departure gate for the booked flight."""
+        flight = self._wizard._bookedFlight
+        if self._wizard.gui.config.onlineGateSystem and \
+           self._wizard.loggedIn and not self._wizard.entranceExam:
+            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)
+
+    def _getSaveDialog(self):
+        """Get the dialog to load a flight file."""
+        if self._saveDialog is not None:
+            return self._saveDialog
+
+        gui = self._wizard.gui
+        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                       xstr("flightsel_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("flightsel_filter_flights"))
+        filter.add_pattern("*.vaflight")
+        dialog.add_filter(filter)
+
+        filter = gtk.FileFilter()
+        filter.set_name(xstr("file_filter_all"))
+        filter.add_pattern("*.*")
+        dialog.add_filter(filter)
+
+        self._saveDialog = dialog
+
+        return dialog
+
+    def _getLoadDialog(self):
+        """Get the dialog to load a flight file."""
+        if self._loadDialog is not None:
+            return self._loadDialog
+
+        gui = self._wizard.gui
+        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
+                                       xstr("flightsel_load_title"),
+                                       action = FILE_CHOOSER_ACTION_OPEN,
+                                       buttons = (gtk.STOCK_CANCEL,
+                                                  RESPONSETYPE_CANCEL,
+                                                  gtk.STOCK_OK, RESPONSETYPE_OK),
+                                       parent = gui.mainWindow)
+        dialog.set_modal(True)
+
+        filter = gtk.FileFilter()
+        filter.set_name(xstr("flightsel_filter_flights"))
+        filter.add_pattern("*.vaflight")
+        dialog.add_filter(filter)
+
+        filter = gtk.FileFilter()
+        filter.set_name(xstr("file_filter_all"))
+        filter.add_pattern("*.*")
+        dialog.add_filter(filter)
+
+        self._loadDialog = dialog
+
+        return dialog
+
+    def _getListPopupMenu(self):
+        """Get the flight list popup menu."""
+        if self._flightListPopupMenu is None:
+            menu = gtk.Menu()
+
+            menuItem = gtk.MenuItem()
+            menuItem.set_label(xstr("flightsel_popup_select"))
+            menuItem.set_use_underline(True)
+            menuItem.connect("activate", self._popupSelect)
+            menuItem.show()
+
+            menu.append(menuItem)
+
+            menuItem = gtk.MenuItem()
+            menuItem.set_label(xstr("flightsel_popup_save"))
+            menuItem.set_use_underline(True)
+            menuItem.connect("activate", self._popupSave)
+            menuItem.show()
+
+            menu.append(menuItem)
+
+            self._flightListPopupMenu = menu
+
+        return self._flightListPopupMenu
+
+    def _popupSelect(self, menuItem):
+        """Called when the Select menu item is activated in the popup menu."""
+        if not self._completed:
+            self._flightSelected()
+
+    def _popupSave(self, menuItem):
+        """Called when the Save menu item is activated in the popup menu."""
+        if not self._completed:
+            self._saveSelected()
+
+#-----------------------------------------------------------------------------
+
+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)
+        self._gateList.connect("row-activated", self._rowActivated)
+
+        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.addCancelFlightButton()
+
+        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 gate in lhbpGates.gates:
+            if gate.isAvailable(lhbpGates, occupiedGateNumbers):
+                self._listStore.append([gate.number])
+
+    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:
+            self._gateSelected()
+        else:
+            self._wizard.nextPage()
+
+    def _rowActivated(self, flightList, path, column):
+        """Called when a row is activated."""
+        if not self._completed:
+            self._gateSelected()
+
+    def _gateSelected(self):
+        """Called when a gate has been selected."""
+        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)
+
+    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"))
+
+        self._selectSimulator = os.name=="nt" or "FORCE_SELECT_SIM" in os.environ
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(7 if self._selectSimulator else 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(9)
+        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)
+
+        if self._selectSimulator:
+            labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0, yalign=0.5)
+            label = gtk.Label(xstr("connect_sim"))
+            labelAlignment.add(label)
+            table.attach(labelAlignment, 0, 1, 5, 7)
+
+            selectAlignment = gtk.Alignment(xalign=0.0, xscale=0.0, yalign=0.5)
+
+            selectBox = gtk.HBox()
+            if pygobject:
+                self._selectMSFS = \
+                  gtk.RadioButton.new_with_mnemonic_from_widget(None,
+                                                                xstr("connect_sim_msfs"))
+            else:
+                self._selectMSFS = gtk.RadioButton(None,
+                                                   xstr("connect_sim_msfs"))
+
+            selectBox.pack_start(self._selectMSFS, False, False, 0);
+
+            if pygobject:
+                self._selectXPlane = \
+                  gtk.RadioButton.new_with_mnemonic_from_widget(self._selectMSFS,
+                                                                xstr("connect_sim_xplane"))
+            else:
+                self._selectXPlane = gtk.RadioButton(self._selectMSFS,
+                                                     xstr("connect_sim_xplane"))
+
+            selectBox.pack_start(self._selectXPlane, False, False, 8);
+
+            selectAlignment.add(selectBox)
+            table.attach(selectAlignment, 1, 2, 5, 7)
+
+
+        self.addCancelFlightButton()
+
+        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 = aircraftNames[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)
+
+        if self._selectSimulator:
+            config = self._wizard.gui.config
+            self._selectMSFS.set_active(config.defaultMSFS)
+            self._selectXPlane.set_active(not config.defaultMSFS)
+
+    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."""
+        if self._selectSimulator:
+            simulatorType = const.SIM_MSFS9 if self._selectMSFS.get_active() \
+                                            else const.SIM_XPLANE10
+        else:
+            simulatorType = const.SIM_MSFS9 if os.name=="nt" \
+              else const.SIM_XPLANE10
+
+        config = self._wizard.gui.config
+        config.defaultMSFS = simulatorType == const.SIM_MSFS9
+        config.save()
+
+        self._wizard._connectSimulator(simulatorType)
+
+    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_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._numCrew = IntegerEntry(defaultValue = 0)
+        self._numCrew.set_width_chars(6)
+        self._numCrew.connect("integer-changed", self._weightChanged)
+        self._numCrew.set_tooltip_text(xstr("payload_crew_tooltip"))
+        table.attach(self._numCrew, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._numCrew)
+
+        label = gtk.Label(xstr("payload_pax"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._numPassengers = IntegerEntry(defaultValue = 0)
+        self._numPassengers.set_width_chars(6)
+        self._numPassengers.connect("integer-changed", self._weightChanged)
+        self._numPassengers.set_tooltip_text(xstr("payload_pax_tooltip"))
+        table.attach(self._numPassengers, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._numPassengers)
+
+        label = gtk.Label(xstr("payload_bag"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 2, 3)
+
+        self._bagWeight = IntegerEntry(defaultValue = 0)
+        self._bagWeight.set_width_chars(6)
+        self._bagWeight.connect("integer-changed", self._weightChanged)
+        self._bagWeight.set_tooltip_text(xstr("payload_bag_tooltip"))
+        table.attach(self._bagWeight, 1, 2, 2, 3)
+        label.set_mnemonic_widget(self._bagWeight)
+
+        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._weightChanged)
+        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_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 4, 5)
+
+        self._mailWeight = IntegerEntry(defaultValue = 0)
+        self._mailWeight.set_width_chars(6)
+        self._mailWeight.connect("integer-changed", self._weightChanged)
+        self._mailWeight.set_tooltip_text(xstr("payload_mail_tooltip"))
+        table.attach(self._mailWeight, 1, 2, 4, 5)
+        label.set_mnemonic_widget(self._mailWeight)
+
+        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.addCancelFlightButton()
+        self._backButton = self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def numCrew(self):
+        """The number of the crew members on the flight."""
+        return self._numCrew.get_int()
+
+    @property
+    def numPassengers(self):
+        """The number of the passengers on the flight."""
+        return self._numPassengers.get_int()
+
+    @property
+    def bagWeight(self):
+        """Get the bag weight entered."""
+        return self._bagWeight.get_int()
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight entered."""
+        return self._cargoWeight.get_int()
+
+    @property
+    def mailWeight(self):
+        """Get the bag weight entered."""
+        return self._mailWeight.get_int()
+
+    def activate(self):
+        """Setup the information."""
+        bookedFlight = self._wizard._bookedFlight
+
+        self._numCrew.set_int(bookedFlight.numCrew)
+        self._numCrew.set_sensitive(True)
+        self._numPassengers.set_int(bookedFlight.numPassengers)
+        self._numPassengers.set_sensitive(True)
+
+        self._bagWeight.set_int(bookedFlight.bagWeight)
+        self._bagWeight.set_sensitive(True)
+        self._cargoWeight.set_int(bookedFlight.cargoWeight)
+        self._cargoWeight.set_sensitive(True)
+        self._mailWeight.set_int(bookedFlight.mailWeight)
+        self._mailWeight.set_sensitive(True)
+
+        self._simulatorZFW.set_text("-")
+        self._simulatorZFWValue = None
+        self._zfwButton.set_sensitive(True)
+        self._updateCalculatedZFW()
+
+    def finalize(self):
+        """Finalize the payload page."""
+        self._numCrew.set_sensitive(False)
+        self._numPassengers.set_sensitive(False)
+        self._bagWeight.set_sensitive(False)
+        self._cargoWeight.set_sensitive(False)
+        self._mailWeight.set_sensitive(False)
+        self._wizard.gui.initializeWeightHelp()
+
+    def calculateZFW(self):
+        """Calculate the ZFW value."""
+        zfw = self._wizard.gui._flight.aircraft.dow
+        zfw += (self._numCrew.get_int() + self._numPassengers.get_int()) * 82
+        zfw += self._bagWeight.get_int()
+        zfw += self._cargoWeight.get_int()
+        zfw += self._mailWeight.get_int()
+        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 _weightChanged(self, entry, weight):
+        """Called when one of the weight values or humanm counts 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.addCancelFlightButton()
+
+        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(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 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 = 0, max = 500)
+        self._cruiseLevel.set_tooltip_text(xstr("route_level_tooltip"))
+        self._cruiseLevel.set_numeric(True)
+        self._cruiseLevel.connect("changed", self._cruiseLevelChanged)
+        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
+        label.set_mnemonic_widget(self._cruiseLevel)
+
+        levelBox.pack_start(self._cruiseLevel, False, False, 8)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(levelBox)
+
+        mainBox.pack_start(alignment, False, False, 0)
+
+
+        routeBox = gtk.VBox()
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        label = gtk.Label(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)
+
+        alternateBox = gtk.HBox()
+
+        label = gtk.Label(xstr("route_altn"))
+        label.set_use_underline(True)
+        alternateBox.pack_start(label, True, True, 0)
+
+        self._alternate = gtk.Entry()
+        self._alternate.set_width_chars(6)
+        self._alternate.connect("changed", self._alternateChanged)
+        self._alternate.set_tooltip_text(xstr("route_altn_tooltip"))
+        label.set_mnemonic_widget(self._alternate)
+
+        alternateBox.pack_start(self._alternate, False, False, 8)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+        alignment.add(alternateBox)
+
+        mainBox.pack_start(alignment, False, False, 0)
+
+        self.addCancelFlightButton()
+
+        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._cruiseLevel.get_value_as_int()
+
+    @property
+    def route(self):
+        """Get the route."""
+        return self._getRoute()
+
+    @property
+    def alternate(self):
+        """Get the ICAO code of the alternate airport."""
+        return self._alternate.get_text()
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._cruiseLevel.set_value(0)
+        self._cruiseLevel.set_text("")
+        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."""
+        cruiseLevelText = self._cruiseLevel.get_text()
+        cruiseLevel = int(cruiseLevelText) if cruiseLevelText else 0
+        alternate = self._alternate.get_text()
+        self._button.set_sensitive(cruiseLevel>=50 and self._getRoute()!="" and
+                                   len(alternate)==4)
+
+    def _cruiseLevelChanged(self, *arg):
+        """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 _alternateChanged(self, entry):
+        """Called when the alternate airport has changed."""
+        entry.set_text(entry.get_text().upper())
+        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.useSimBrief:
+            self._wizard.nextPage()
+        else:
+            self._wizard.jumpPage(3)
+
+#-----------------------------------------------------------------------------
+
+class SimBriefSetupPage(Page):
+    """Page for setting up some parameters for SimBrief."""
+    monthNum2Name = [
+        "JAN",
+        "FEB",
+        "MAR",
+        "APR",
+        "MAY",
+        "JUN",
+        "JUL",
+        "AUG",
+        "SEP",
+        "OCT",
+        "NOV",
+        "DEC"
+        ]
+
+    @staticmethod
+    def getHTMLFilePath():
+        """Get the path of the HTML file to contain the generated flight
+        plan."""
+        if os.name=="nt":
+            return os.path.join(tempfile.gettempdir(),
+                                "mlx_simbrief" +
+                                (".secondary" if secondaryInstallation else "") +
+                                ".html")
+        else:
+            import pwd
+            return os.path.join(tempfile.gettempdir(),
+                                "mlx_simbrief." + pwd.getpwuid(os.getuid())[0] + "" +
+                                (".secondary" if secondaryInstallation else "") +
+                                ".html")
+
+    def __init__(self, wizard):
+        """Construct the setup page."""
+
+        super(SimBriefSetupPage, self).__init__(wizard,
+                                                "SimBrief setup",
+                                                "Complete the following data to "
+                                                "start generating your "
+                                                "SimBrief briefing.",
+                                                "Your SimBrief briefing was "
+                                                "generated with the following "
+                                                "data.")
+
+        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("_Username:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 0, 1)
+
+        self._userName = gtk.Entry()
+        self._userName.set_width_chars(16)
+        self._userName.connect("changed",
+                               lambda button: self._updateForwardButton())
+        self._userName.set_tooltip_text("Your SimBrief username")
+        table.attach(self._userName, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self._userName)
+
+        label = gtk.Label("_Password:")
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, 1, 2)
+
+        self._password = gtk.Entry()
+        self._password.set_visibility(False)
+        self._password.connect("changed",
+                               lambda button: self._updateForwardButton())
+        self._password.set_tooltip_text("Your SimBrief password")
+        table.attach(self._password, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self._password)
+
+        self._rememberButton = gtk.CheckButton(xstr("simbrief_remember_password"))
+        self._rememberButton.set_use_underline(True)
+        self._rememberButton.set_tooltip_text(xstr("simbrief_remember_tooltip"))
+        table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
+
+        self.addCancelFlightButton()
+
+        self._backButton = self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    def activate(self):
+        """Activate the SimBrief setup page"""
+        config = self._wizard.gui.config
+
+        self._userName.set_text(config.simBriefUserName)
+        self._password.set_text(config.simBriefPassword)
+        self._rememberButton.set_active(config.rememberSimBriefPassword)
+
+        self._updateForwardButton()
+
+
+    def _updateForwardButton(self):
+        """Update the sensitivity of the forward button."""
+        self._button.set_sensitive(len(self._userName.get_text())>0 and
+                                   len(self._password.get_text())>0)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _forwardClicked(self, button):
+        if self._completed:
+            self._wizard.nextPage()
+        else:
+            config = self._wizard.gui.config
+
+            config.simBriefUserName = self._userName.get_text()
+
+            rememberPassword = self._rememberButton.get_active()
+            config.simBriefPassword = \
+              self._password.get_text() if rememberPassword else ""
+            config.rememberSimBriefPassword = rememberPassword
+
+            config.save()
+
+            plan = self._getPlan()
+            print "plan:", plan
+
+            userName = self._userName.get_text()
+            password = self._password.get_text()
+
+            self._wizard.gui.beginBusy("Calling SimBrief...")
+
+            cef.callSimBrief(plan,
+                             lambda count: (userName, password),
+                             self._simBriefProgressCallback,
+                             SimBriefSetupPage.getHTMLFilePath())
+
+            startSound(const.SOUND_NOTAM)
+
+    def _simBriefProgressCallback(self, message, done):
+        """Called by the SimBrief handling thread."""
+        gobject.idle_add(self._simBriefProgress, message, done)
+
+    def _simBriefProgress(self, message, done):
+        """The real SimBrief progress handler."""
+        if done:
+            self._wizard.gui.endBusy()
+            self._wizard.nextPage()
+        else:
+            self._wizard.gui.updateBusyState(message)
+
+    def _getPlan(self):
+        """Get the flight plan data for SimBrief."""
+        plan = {
+            "airline": "MAH",
+            "selcal": "XXXX",
+            "fuelfactor": "P000",
+            "contpct": "0.05",
+            "resvrule": "45",
+            "taxiout": "10",
+            "taxiin": "10",
+            "civalue": "AUTO"
+            }
+
+        wizard = self._wizard
+        gui = wizard.gui
+
+        loginResult = wizard.loginResult
+        plan["cpt"] = loginResult.pilotName
+        plan["pid"] = loginResult.pilotID
+
+        bookedFlight = wizard.bookedFlight
+        plan["fltnum"] = wizard.bookedFlight.callsign[2:]
+        plan["type"] = const.icaoCodes[bookedFlight.aircraftType]
+        plan["orig"] = bookedFlight.departureICAO
+        plan["dest"] = bookedFlight.arrivalICAO
+        plan["reg"] = bookedFlight.tailNumber
+        plan["fin"] = bookedFlight.tailNumber[3:]
+        plan["pax"] = str(bookedFlight.numPassengers)
+
+        departureTime = bookedFlight.departureTime
+        plan["date"] = "%d%s%d" % (departureTime.day,
+                                   SimBriefSetupPage.monthNum2Name[departureTime.month-1],
+                                   departureTime.year%100)
+        plan["deph"] = str(departureTime.hour)
+        plan["depm"] = str(departureTime.minute)
+
+        arrivalTime = bookedFlight.arrivalTime
+        plan["steh"] = str(arrivalTime.hour)
+        plan["stem"] = str(arrivalTime.minute)
+
+        plan["manualzfw"] = str(wizard.zfw)
+        plan["cargo"] = str(wizard.bagWeight + wizard.cargoWeight + wizard.mailWeight)
+
+        plan["route"] = wizard.route
+        plan["fl"] = str(wizard.filedCruiseAltitude)
+        plan["altn"] = wizard.alternate
+
+        plan["addedfuel"] = "2.5" # FIXME: query
+        plan["origrwy"] = "" # FIXME: query
+        plan["destrwy"] = "" # FIXME: query
+        plan["climb"] = "250/300/78" # FIXME: query
+        plan["descent"] = "80/280/250" # FIXME: query
+        plan["cruise"] = "LRC" # FIXME: query
+
+        return plan
+
+#-----------------------------------------------------------------------------
+
+class SimBriefingPage(Page):
+    """Page to display the SimBrief HTML briefing."""
+    def __init__(self, wizard):
+        """Construct the setup page."""
+
+        super(SimBriefingPage, self).__init__(wizard,
+                                              "SimBrief flight plan", "")
+
+        self._alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                       xscale = 1.0, yscale = 1.0)
+
+        self.setMainWidget(self._alignment)
+
+        self.addCancelFlightButton()
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+        self._button.set_label(xstr("briefing_button"))
+        self._button.set_has_tooltip(False)
+        self._button.set_use_stock(False)
+
+    def activate(self):
+        """Activate the SimBrief flight plan page"""
+        container = cef.getContainer()
+        self._alignment.add(container)
+
+        self._browser = \
+          cef.startInContainer(container,
+                               "file://" + SimBriefSetupPage.getHTMLFilePath())
+
+    def _backClicked(self, button):
+        """Called when the Back button has been pressed."""
+        self.goBack()
+
+    def _forwardClicked(self, button):
+        """Called when the Forward button has been pressed."""
+        if not self._completed:
+            self._button.set_label(xstr("button_next"))
+            self._button.set_tooltip_text(xstr("button_next_tooltip"))
+            self._wizard.usingSimBrief = True
+            self.complete()
+
+        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_pre") +
+                                       xstr("fuel_help_post"),
+                                       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)
+
+        tankData = [(tank, 2500, 3900) for tank in acft.mostFuelTanks]
+        self._setupTanks(tankData)
+
+        self.addCancelFlightButton()
+
+        self._backButton = self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+        self._pumpIndex = 0
+
+    def activate(self):
+        """Activate the page."""
+        self._setupTanks(self._wizard._fuelData)
+
+        aircraft = self._wizard.gui.flight.aircraft
+        minLandingFuel = aircraft.minLandingFuel
+        recommendedLandingFuel = aircraft.recommendedLandingFuel
+
+        middleHelp = "" if minLandingFuel is None else \
+            (xstr("fuel_help_min") % (minLandingFuel,)) \
+            if recommendedLandingFuel is None else \
+            (xstr("fuel_help_min_rec") % (minLandingFuel,
+                                          recommendedLandingFuel))
+        self.setHelp(xstr("fuel_help_pre") + middleHelp + xstr("fuel_help_post"))
+
+    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()
+        elif self._wizard.usingSimBrief:
+            self._wizard.jumpPage(3)
+        else:
+            self._wizard.nextPage()
+
+    def _setupTanks(self, tankData):
+        """Setup the tanks for the given data."""
+        numTanks = len(tankData)
+        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)
+        index = 0
+        for (tank, current, capacity) in tankData:
+            fuelTank = FuelTank(tank,
+                                xstr("fuel_tank_" +
+                                     const.fuelTank2string(tank)),
+                                capacity, current)
+            self._fuelTable.attach(fuelTank, index, index+1, 0, 1)
+            self._fuelTanks.append(fuelTank)
+            index += 1
+
+        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()
+            if self._wizard.usingSimBrief:
+                self._wizard.gui.startMonitoring()
+                self._wizard.jumpPage(3)
+            else:
+                bookedFlight = self._wizard._bookedFlight
+                self._wizard.gui.beginBusy(xstr("route_down_notams"))
+                self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
+                                                      bookedFlight.departureICAO,
+                                                      bookedFlight.arrivalICAO)
+                startSound(const.SOUND_NOTAM)
+        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)
+
+    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._updatingMETAR = 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.addCancelFlightButton()
+
+        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 changeMETAR(self, metar):
+        """Change the METAR as a result of an edit on one of the other
+        pages."""
+        self._updatingMETAR = True
+        self._metar.get_buffer().set_text(metar)
+        self._updatingMETAR = False
+
+        self._updateButton()
+        self.metarEdited = True
+
+    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)
+                s += "-------------------- * --------------------\n"
+            buffer.set_text(s)
+
+        self._metarFrame.set_label(xstr("briefing_metar_template") % (icao,))
+        buffer = self._metar.get_buffer()
+        self._updatingMETAR = True
+        if metar is None:
+            buffer.set_text("")
+            self.setHelp(xstr("briefing_help_nometar"))
+        else:
+            buffer.set_text(metar)
+        self._updatingMETAR = False
+        self._updateButton()
+
+        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."""
+        print "BriefingPage.metarChanged", self._updatingMETAR
+        if not self._updatingMETAR:
+            self.metarEdited = True
+            self._updateButton()
+            metar = buffer.get_text(buffer.get_start_iter(),
+                                buffer.get_end_iter(), True)
+            self._wizard.metarChanged(metar, self)
+
+    def _metarInserted(self, textBuffer, iter, text, length):
+        """Called when new characters are inserted into the METAR.
+
+        It uppercases all characters."""
+        print "BriefingPage.metarInserted", self._updatingMETAR
+        if not self._updatingMETAR:
+            self._updatingMETAR = True
+
+            iter1 = iter.copy()
+            iter1.backward_chars(length)
+            textBuffer.delete(iter, iter1)
+
+            textBuffer.insert(iter, text.upper())
+
+            self._updatingMETAR = False
+
+    def _updateButton(self):
+        """Update the sensitivity of the Next button based on the contents of
+        the METAR field."""
+        buffer = self._metar.get_buffer()
+        self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
+                                                   buffer.get_end_iter(),
+                                                   True)!="")
+
+
+#-----------------------------------------------------------------------------
+
+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(9, 24)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        label = gtk.Label(xstr("takeoff_metar"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, row, row+1)
+
+        self._metar = gtk.Entry()
+        self._metar.set_width_chars(40)
+        self._metar.set_tooltip_text(xstr("takeoff_metar_tooltip"))
+        self._metar.connect("changed", self._metarChanged)
+        self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
+        table.attach(self._metar, 1, 24, row, row+1)
+        label.set_mnemonic_widget(self._metar)
+
+        self._updatingMETAR = False
+
+        row += 1
+
+        label = gtk.Label(xstr("takeoff_runway"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, row, row+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, row, row+1)
+        label.set_mnemonic_widget(self._runway)
+
+        row += 1
+
+        label = gtk.Label(xstr("takeoff_sid"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, row, row+1)
+
+        if pygobject:
+            self._sid = gtk.ComboBox.new_with_model_and_entry(comboModel)
+        else:
+            self._sid = gtk.ComboBoxEntry(comboModel)
+
+        self._sid.set_entry_text_column(0)
+        self._sid.get_child().set_width_chars(10)
+        self._sid.set_tooltip_text(xstr("takeoff_sid_tooltip"))
+        self._sid.connect("changed", self._upperChangedComboBox)
+        table.attach(self._sid, 1, 3, row, row+1)
+        label.set_mnemonic_widget(self._sid)
+
+        row += 1
+
+        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, row, row+1)
+
+        self._v1 = IntegerEntry()
+        self._v1.set_width_chars(4)
+        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip_knots"))
+        self._v1.connect("integer-changed", self._valueChanged)
+        table.attach(self._v1, 2, 3, row, row+1)
+        label.set_mnemonic_widget(self._v1)
+
+        self._v1Unit = gtk.Label(xstr("label_knots"))
+        self._v1Unit.set_alignment(0.0, 0.5)
+        table.attach(self._v1Unit, 3, 4, row, row+1)
+
+        row += 1
+
+        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, row, row+1)
+
+        self._vr = IntegerEntry()
+        self._vr.set_width_chars(4)
+        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip_knots"))
+        self._vr.connect("integer-changed", self._valueChanged)
+        table.attach(self._vr, 2, 3, row, row+1)
+        label.set_mnemonic_widget(self._vr)
+
+        self._vrUnit = gtk.Label(xstr("label_knots"))
+        self._vrUnit.set_alignment(0.0, 0.5)
+        table.attach(self._vrUnit, 3, 4, row, row+1)
+
+        row += 1
+
+        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, row, row+1)
+
+        self._v2 = IntegerEntry()
+        self._v2.set_width_chars(4)
+        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip_knots"))
+        self._v2.connect("integer-changed", self._valueChanged)
+        table.attach(self._v2, 2, 3, row, row+1)
+        label.set_mnemonic_widget(self._v2)
+
+        self._v2Unit = gtk.Label(xstr("label_knots"))
+        self._v2Unit.set_alignment(0.0, 0.5)
+        table.attach(self._v2Unit, 3, 4, row, row+1)
+
+        row += 1
+
+        self._derateType = acft.DERATE_NONE
+
+        self._derateLabel = gtk.Label()
+        self._derateLabel.set_use_underline(True)
+        self._derateLabel.set_markup(xstr("takeoff_derate_tupolev"))
+        self._derateLabel.set_alignment(0.0, 0.5)
+        table.attach(self._derateLabel, 0, 1, row, row+1)
+
+        self._derate = gtk.Alignment()
+        table.attach(self._derate, 2, 4, row, row+1)
+        self._derateWidget = None
+        self._derateEntry = None
+        self._derateUnit = None
+        self._derateButtons = None
+
+        row += 1
+
+        self._antiIceOn = gtk.CheckButton(xstr("takeoff_antiice"))
+        self._antiIceOn.set_use_underline(True)
+        self._antiIceOn.set_tooltip_text(xstr("takeoff_antiice_tooltip"))
+        table.attach(self._antiIceOn, 2, 4, row, row+1)
+
+        row += 1
+
+        self._rto = gtk.CheckButton(xstr("takeoff_rto"))
+        self._rto.set_use_underline(True)
+        self._rto.set_tooltip_text(xstr("takeoff_rto_tooltip"))
+        self._rto.connect("toggled", self._rtoToggled)
+        table.attach(self._rto, 2, 4, row, row+1, ypadding = 8)
+
+        self.addCancelFlightButton()
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+        self._active = False
+
+    @property
+    def runway(self):
+        """Get the runway."""
+        return self._runway.get_text()
+
+    @property
+    def sid(self):
+        """Get the SID."""
+        text = self._sid.get_child().get_text()
+        return text if self._sid.get_active()!=0 and text and text!="N/A" \
+               else None
+
+    @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()
+
+    @property
+    def derate(self):
+        """Get the derate value, if any."""
+        if self._derateWidget is None:
+            return None
+        if self._derateType==acft.DERATE_BOEING:
+            derate = self._derateEntry.get_text()
+            return derate if derate else None
+        elif self._derateType==acft.DERATE_EPR:
+            derate = self._derateWidget.get_text()
+            return derate if derate else None
+        elif self._derateType==acft.DERATE_TUPOLEV:
+            return acft.DERATE_TUPOLEV_NOMINAL \
+                   if self._derateButtons[0].get_active() \
+                   else acft.DERATE_TUPOLEV_TAKEOFF
+        elif self._derateType==acft.DERATE_B462:
+            return self._derateWidget.get_active()
+        else:
+            return None
+
+    @property
+    def antiIceOn(self):
+        """Get whether the anti-ice system has been turned on."""
+        return self._antiIceOn.get_active()
+
+    @antiIceOn.setter
+    def antiIceOn(self, value):
+        """Set the anti-ice indicator."""
+        self._antiIceOn.set_active(value)
+
+    @property
+    def rtoIndicated(self):
+        """Get whether the pilot has indicated if there was an RTO."""
+        return self._rto.get_active()
+
+    def activate(self):
+        """Activate the page."""
+        print "TakeoffPage.activate"
+
+        self._updatingMETAR = True
+        self._metar.get_buffer().set_text(self._wizard.departureMETAR, -1)
+        self._updatingMETAR = False
+
+        self._runway.set_text("")
+        self._runway.set_sensitive(True)
+        self._sid.set_active(0)
+        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)
+
+        i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
+        speedUnit = xstr("label" + i18nSpeedUnit)
+        self._v1Unit.set_text(speedUnit)
+        self._vrUnit.set_text(speedUnit)
+        self._v2Unit.set_text(speedUnit)
+
+        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip" + i18nSpeedUnit))
+        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip" + i18nSpeedUnit))
+        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip" + i18nSpeedUnit))
+
+        self._derateType = self._wizard.gui.flight.aircraft.derateType
+
+        self._setupDerateWidget()
+
+        self._rto.set_active(False)
+        self._rto.set_sensitive(False)
+
+        self._button.set_sensitive(False)
+        self._forwardAllowed = False
+
+        self._active = True
+
+    def allowForward(self):
+        """Allow going to the next page."""
+        print "TakeoffPage.allowForward"
+        self._forwardAllowed = True
+        self._updateForwardButton()
+
+    def reset(self):
+        """Reset the page if the wizard is reset."""
+        print "TakeoffPage.reset"
+
+        super(TakeoffPage, self).reset()
+        self._v1.reset()
+        self._vr.reset()
+        self._v2.reset()
+        self._hasDerate = False
+        self._antiIceOn.set_active(False)
+        self._active = False
+
+    def setRTOEnabled(self, enabled):
+        """Set the RTO checkbox enabled or disabled."""
+        if not enabled:
+            self._rto.set_active(False)
+        self._rto.set_sensitive(enabled)
+
+    def changeMETAR(self, metar):
+        """Change the METAR as a result of an edit on one of the other
+        pages."""
+        if self._active:
+            print "TakeoffPage.changeMETAR"
+            self._updatingMETAR = True
+            self._metar.get_buffer().set_text(metar, -1)
+            self._updatingMETAR = False
+
+            self._updateForwardButton()
+
+    def _updateForwardButton(self):
+        """Update the sensitivity of the forward button based on some conditions."""
+        sensitive = self._forwardAllowed and \
+                    self._metar.get_text()!="" and \
+                    self._runway.get_text()!="" and \
+                    self.sid is not None 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 and \
+                    (self._derateType==acft.DERATE_NONE or
+                     self.derate is not None)
+
+        print "TakeoffPage._updateForwardButton: forwardAllowed:", self._forwardAllowed, ", sensitive:", sensitive
+        if self._forwardAllowed:
+            print "  METAR: ", self._metar.get_text()
+            print "  runway: ", self._runway.get_text()
+            print "  SID:", self.sid
+            print "  V1:", self.v1
+            print "  VR:", self.vr
+            print "  V2:", self.v2
+            print "  derateType:", self._derateType
+            print "  derate:", self.derate
+
+        self._button.set_sensitive(sensitive)
+
+    def _valueChanged(self, widget, arg = None):
+        """Called when the value of some widget has changed."""
+        print "TakeoffPage._valueChanged"
+
+        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."""
+        print "TakeoffPage._upperChanged"
+        entry.set_text(entry.get_text().upper())
+        self._valueChanged(entry, arg)
+
+    def _upperChangedComboBox(self, comboBox):
+        """Called for combo box widgets that must be converted to uppercase."""
+        entry = comboBox.get_child()
+        if comboBox.get_active()==-1:
+            entry.set_text(entry.get_text().upper())
+        self._valueChanged(entry)
+
+    def _derateChanged(self, entry):
+        """Called when the value of the derate is changed."""
+        print "TakeoffPage._derateChanged"
+        self._updateForwardButton()
+
+    def _rtoToggled(self, button):
+        """Called when the RTO check button is toggled."""
+        self._wizard.rtoToggled(button.get_active())
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _forwardClicked(self, button):
+        """Called when the forward button is clicked."""
+        aircraft = self._wizard.gui.flight.aircraft
+        aircraft.updateV1R2()
+        if self.derate is not None:
+            aircraft.updateDerate()
+        aircraft.updateTakeoffAntiIce()
+        self._wizard.nextPage()
+
+    def _setupDerateWidget(self):
+        """Setup the derate widget."""
+        if self._derateWidget is not None:
+            self._derate.remove(self._derateWidget)
+
+        if self._derateType==acft.DERATE_BOEING:
+            self._derateLabel.set_text(xstr("takeoff_derate_boeing"))
+            self._derateLabel.set_use_underline(True)
+            self._derateLabel.set_sensitive(True)
+
+            self._derateEntry = gtk.Entry()
+            self._derateEntry.set_width_chars(7)
+            self._derateEntry.set_tooltip_text(xstr("takeoff_derate_boeing_tooltip"))
+            self._derateEntry.set_alignment(1.0)
+            self._derateEntry.connect("changed", self._derateChanged)
+            self._derateLabel.set_mnemonic_widget(self._derateEntry)
+
+            self._derateUnit = gtk.Label("%")
+            self._derateUnit.set_alignment(0.0, 0.5)
+
+            self._derateWidget = gtk.Table(3, 1)
+            self._derateWidget.set_row_spacings(4)
+            self._derateWidget.set_col_spacings(16)
+            self._derateWidget.set_homogeneous(False)
+
+            self._derateWidget.attach(self._derateEntry, 0, 2, 0, 1)
+            self._derateWidget.attach(self._derateUnit, 2, 3, 0, 1)
+
+            self._derate.add(self._derateWidget)
+        elif self._derateType==acft.DERATE_EPR:
+            self._derateLabel.set_text("_EPR:")
+            self._derateLabel.set_use_underline(True)
+            self._derateLabel.set_sensitive(True)
+
+            self._derateWidget = gtk.Entry()
+            self._derateWidget.set_width_chars(7)
+            self._derateWidget.set_tooltip_text(xstr("takeoff_derate_epr_tooltip"))
+            self._derateWidget.set_alignment(1.0)
+            self._derateWidget.connect("changed", self._derateChanged)
+            self._derateLabel.set_mnemonic_widget(self._derateWidget)
+
+            self._derate.add(self._derateWidget)
+        elif self._derateType==acft.DERATE_TUPOLEV:
+            self._derateLabel.set_text(xstr("takeoff_derate_tupolev"))
+            self._derateLabel.set_use_underline(True)
+            self._derateLabel.set_sensitive(True)
+
+            if pygobject:
+                nominal = gtk.RadioButton.\
+                  new_with_label_from_widget(None,
+                                             xstr("takeoff_derate_tupolev_nominal"))
+            else:
+                nominal = gtk.RadioButton(None,
+                                          xstr("takeoff_derate_tupolev_nominal"))
+            nominal.set_use_underline(True)
+            nominal.set_tooltip_text(xstr("takeoff_derate_tupolev_nominal_tooltip"))
+            nominal.connect("toggled", self._derateChanged)
+
+            if pygobject:
+                takeoff = gtk.RadioButton.\
+                  new_with_label_from_widget(nominal,
+                                             xstr("takeoff_derate_tupolev_takeoff"))
+            else:
+                takeoff = gtk.RadioButton(nominal,
+                                          xstr("takeoff_derate_tupolev_takeoff"))
+
+            takeoff.set_use_underline(True)
+            takeoff.set_tooltip_text(xstr("takeoff_derate_tupolev_takeoff_tooltip"))
+            takeoff.connect("toggled", self._derateChanged)
+
+            self._derateButtons = [nominal, takeoff]
+
+            self._derateWidget = gtk.HBox()
+            self._derateWidget.pack_start(nominal, False, False, 4)
+            self._derateWidget.pack_start(takeoff, False, False, 4)
+
+            self._derate.add(self._derateWidget)
+        elif self._derateType==acft.DERATE_B462:
+            self._derateLabel.set_text("")
+
+            self._derateWidget = gtk.CheckButton(xstr("takeoff_derate_b462"))
+            self._derateWidget.set_tooltip_text(xstr("takeoff_derate_b462_tooltip"))
+            self._derateWidget.set_use_underline(True)
+            self._derate.add(self._derateWidget)
+        else:
+            self._derateWidget = None
+            self._derateLabel.set_text("")
+            self._derateLabel.set_sensitive(False)
+
+    def _metarChanged(self, entry):
+        """Called when the METAR has changed."""
+        print "TakeoffPage.metarChanged", self._updatingMETAR
+        if not self._updatingMETAR:
+            self._updateForwardButton()
+            self._wizard.metarChanged(entry.get_text(), self)
+
+    def _metarInserted(self, buffer, position, text, length):
+        """Called when new characters are inserted into the METAR.
+
+        It uppercases all characters."""
+        print "TakeoffPage.metarInserted", self._updatingMETAR
+        if not self._updatingMETAR:
+            self._updatingMETAR = True
+
+            buffer.delete_text(position, length)
+            buffer.insert_text(position, text.upper(), length)
+
+            self._updatingMETAR = False
+
+#-----------------------------------------------------------------------------
+
+class CruisePage(Page):
+    """The page containing the flight level that might change during flight."""
+    def __init__(self, wizard):
+        """Construct the page."""
+        super(CruisePage, self).__init__(wizard, xstr("cruise_title"),
+                                         xstr("cruise_help"))
+
+        self._loggable = False
+        self._loggedCruiseLevel = 240
+        self._activated = False
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
+                                  xscale = 0.0, yscale = 1.0)
+
+        mainBox = gtk.VBox()
+        alignment.add(mainBox)
+        self.setMainWidget(alignment)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 0.0)
+        mainBox.pack_start(alignment, False, False, 16)
+
+        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("cruise_route_level_tooltip"))
+        self._cruiseLevel.set_numeric(True)
+        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
+        label.set_mnemonic_widget(self._cruiseLevel)
+
+        levelBox.pack_start(self._cruiseLevel, False, False, 8)
+
+        self._updateButton = gtk.Button(xstr("cruise_route_level_update"));
+        self._updateButton.set_use_underline(True)
+        self._updateButton.set_tooltip_text(xstr("cruise_route_level_update_tooltip"))
+        self._updateButton.connect("clicked", self._updateButtonClicked)
+
+        levelBox.pack_start(self._updateButton, False, False, 16)
+
+        mainBox.pack_start(levelBox, False, False, 0)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 0.0, yscale = 1.0)
+        mainBox.pack_start(alignment, True, True, 0)
+
+        self.addCancelFlightButton()
+
+        self._backButton = self.addPreviousButton(clicked = self._backClicked)
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+    @property
+    def activated(self):
+        """Determine if the page is already activated or not."""
+        return self._activated
+
+    @property
+    def cruiseLevel(self):
+        """Get the cruise level."""
+        return self._loggedCruiseLevel
+
+    @property
+    def loggableCruiseLevel(self):
+        """Get the cruise level which should be logged."""
+        return self._cruiseLevel.get_value_as_int()
+
+    def setLoggable(self, loggable):
+        """Set whether the cruise altitude can be logged."""
+        self._loggable = loggable
+        self._updateButtons()
+
+    def activate(self):
+        """Setup the route from the booked flight."""
+        self._loggedCruiseLevel = self._wizard.filedCruiseLevel
+        self._cruiseLevel.set_value(self._loggedCruiseLevel)
+        self._activated = True
+
+    def reset(self):
+        """Reset the page."""
+        self._loggable = False
+        self._activated = False
+        super(CruisePage, self).reset()
+
+    def _updateButtons(self):
+        """Update the sensitivity of the buttons."""
+        self._updateButton.set_sensitive(self._loggable and
+                                         self.loggableCruiseLevel!=
+                                         self._loggedCruiseLevel)
+
+    def _cruiseLevelChanged(self, spinButton):
+        """Called when the cruise level has changed."""
+        self._updateButtons()
+
+    def _updateButtonClicked(self, button):
+        """Called when the update button is clicked."""
+        if self._wizard.cruiseLevelChanged():
+            self._loggedCruiseLevel = self.loggableCruiseLevel
+            self._updateButtons()
+
+    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(7, 24)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        label = gtk.Label(xstr("landing_metar"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 0, 1, row, row+1)
+
+        self._metar = gtk.Entry()
+        self._metar.set_width_chars(40)
+        self._metar.set_tooltip_text(xstr("landing_metar_tooltip"))
+        self._metar.connect("changed", self._metarChanged)
+        self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
+        table.attach(self._metar, 1, 24, row, row+1)
+        label.set_mnemonic_widget(self._metar)
+
+        self._updatingMETAR = False
+
+        row += 1
+
+        label = gtk.Label(xstr("landing_star"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, row, row + 1)
+
+        if pygobject:
+            self._star = gtk.ComboBox.new_with_model_and_entry(comboModel)
+        else:
+            self._star = gtk.ComboBoxEntry(comboModel)
+
+        self._star.set_entry_text_column(0)
+        self._star.get_child().set_width_chars(10)
+        self._star.set_tooltip_text(xstr("landing_star_tooltip"))
+        self._star.connect("changed", self._upperChangedComboBox)
+        self._star.set_sensitive(False)
+        table.attach(self._star, 2, 4, row, row + 1)
+        label.set_mnemonic_widget(self._star)
+
+        row += 1
+
+        label = gtk.Label(xstr("landing_transition"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, row, row + 1)
+
+        if pygobject:
+            self._transition = gtk.ComboBox.new_with_model_and_entry(comboModel)
+        else:
+            self._transition = gtk.ComboBoxEntry(comboModel)
+
+        self._transition.set_entry_text_column(0)
+        self._transition.get_child().set_width_chars(10)
+        self._transition.set_tooltip_text(xstr("landing_transition_tooltip"))
+        self._transition.connect("changed", self._upperChangedComboBox)
+        self._transition.set_sensitive(False)
+        table.attach(self._transition, 2, 4, row, row + 1)
+        label.set_mnemonic_widget(self._transition)
+
+        row += 1
+
+        label = gtk.Label(xstr("landing_runway"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, row, row + 1)
+
+        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, row, row + 1)
+        label.set_mnemonic_widget(self._runway)
+
+        row += 1
+
+        label = gtk.Label(xstr("landing_approach"))
+        label.set_use_underline(True)
+        label.set_alignment(0.0, 0.5)
+        table.attach(label, 1, 2, row, row + 1)
+
+        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, row, row + 1)
+        label.set_mnemonic_widget(self._approachType)
+
+        row += 1
+
+        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, row, row + 1)
+
+        self._vref = IntegerEntry()
+        self._vref.set_width_chars(5)
+        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip_knots"))
+        self._vref.connect("integer-changed", self._vrefChanged)
+        table.attach(self._vref, 3, 4, row, row + 1)
+        label.set_mnemonic_widget(self._vref)
+
+        self._vrefUnit = gtk.Label(xstr("label_knots"))
+        table.attach(self._vrefUnit, 4, 5, row, row + 1)
+
+        row += 1
+
+        self._antiIceOn = gtk.CheckButton(xstr("landing_antiice"))
+        self._antiIceOn.set_use_underline(True)
+        self._antiIceOn.set_tooltip_text(xstr("landing_antiice_tooltip"))
+        table.attach(self._antiIceOn, 3, 5, row, row + 1)
+
+        self.addCancelFlightButton()
+
+        self.addPreviousButton(clicked = self._backClicked)
+
+        self._button = self.addNextButton(clicked = self._forwardClicked)
+
+        self._active = False
+
+    @property
+    def star(self):
+        """Get the STAR or None if none entered."""
+        text = self._star.get_child().get_text()
+        return text if self._star.get_active()!=0 and text and text!="N/A" \
+               else None
+
+    @property
+    def transition(self):
+        """Get the transition or None if none entered."""
+        text = self._transition.get_child().get_text()
+        return text if self._transition.get_active()!=0 and text and text!="N/A" \
+               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()
+
+    @property
+    def antiIceOn(self):
+        """Get whether the anti-ice system has been turned on."""
+        return self._antiIceOn.get_active()
+
+    @antiIceOn.setter
+    def antiIceOn(self, value):
+        """Set the anti-ice indicator."""
+        self._antiIceOn.set_active(value)
+
+    def reset(self):
+        """Reset the page if the wizard is reset."""
+        super(LandingPage, self).reset()
+        self._vref.reset()
+        self._antiIceOn.set_active(False)
+        self._flightEnded = False
+        self._active = False
+
+    def activate(self):
+        """Called when the page is activated."""
+        self._updatingMETAR = True
+        self._metar.get_buffer().set_text(self._wizard.arrivalMETAR, -1)
+        self._updatingMETAR = False
+
+        self._star.set_active(0)
+        self._star.set_sensitive(True)
+
+        self._transition.set_active(0)
+        self._transition.set_sensitive(True)
+
+        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)
+
+        i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
+        speedUnit = xstr("label" + i18nSpeedUnit)
+        self._vrefUnit.set_text(speedUnit)
+
+        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip" +
+                                           i18nSpeedUnit))
+
+        self._updateForwardButton()
+
+        self._active = True
+
+    def flightEnded(self):
+        """Called when the flight has ended."""
+        super(LandingPage, self).flightEnded()
+        self._flightEnded = True
+        self._updateForwardButton()
+
+    def changeMETAR(self, metar):
+        """Change the METAR as a result of an edit on one of the other
+        pages."""
+        if self._active:
+            print "LandingPage.changeMETAR"
+            self._updatingMETAR = True
+            self._metar.get_buffer().set_text(metar, -1)
+            self._updatingMETAR = False
+
+            self._updateForwardButton()
+
+    def _updateForwardButton(self):
+        """Update the sensitivity of the forward button."""
+        sensitive = self._flightEnded and \
+                    self._metar.get_text()!="" and \
+                    (self.star is not None or
+                     self.transition is not None) 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 _upperChangedComboBox(self, comboBox):
+        """Called for combo box widgets that must be converted to uppercase."""
+        if comboBox.get_active()==-1:
+            entry = comboBox.get_child()
+            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."""
+        aircraft = self._wizard.gui.flight.aircraft
+        aircraft.updateVRef()
+        aircraft.updateLandingAntiIce()
+        if self._wizard.gui.config.onlineGateSystem and \
+           self._wizard.loggedIn and not self._completed and \
+           self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
+           not self._wizard.entranceExam:
+            self._wizard.getFleet(callback = self._fleetRetrieved,
+                                  force = True)
+        else:
+            self._wizard.nextPage()
+
+    def _fleetRetrieved(self, fleet):
+        """Callback for the fleet retrieval."""
+        self._wizard.nextPage()
+
+    def _metarChanged(self, entry):
+        """Called when the METAR has changed."""
+        print "LandingPage.metarChanged", self._updatingMETAR
+        if not self._updatingMETAR:
+            self._updateForwardButton()
+            self._wizard.metarChanged(entry.get_text(), self)
+
+    def _metarInserted(self, buffer, position, text, length):
+        """Called when new characters are inserted into the METAR.
+
+        It uppercases all characters."""
+        print "LandingPage.metarInserted", self._updatingMETAR
+        if not self._updatingMETAR:
+            self._updatingMETAR = True
+
+            buffer.delete_text(position, length)
+            buffer.insert_text(position, text.upper(), length)
+
+            self._updatingMETAR = False
+
+#-----------------------------------------------------------------------------
+
+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."""
+        help = xstr("finish_help") + xstr("finish_help_goodtime")
+        super(FinishPage, self).__init__(wizard, xstr("finish_title"), help)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                  xscale = 0.0, yscale = 0.0)
+
+        table = gtk.Table(10, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(False)
+        alignment.add(table)
+        self.setMainWidget(alignment)
+
+        row = 0
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_rating"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+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, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_dep_time"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._depTime = gtk.Label()
+        self._depTime.set_width_chars(13)
+        self._depTime.set_alignment(0.0, 0.5)
+        self._depTime.set_use_markup(True)
+        self._depTime.set_tooltip_markup(xstr("finish_dep_time_tooltip"))
+        labelAlignment.add(self._depTime)
+        table.attach(labelAlignment, 1, 2, row, row+1)
+
+        row += 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, row, row+1)
+
+        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, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_block_time"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        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, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_arr_time"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
+        self._arrTime = gtk.Label()
+        self._arrTime.set_width_chars(13)
+        self._arrTime.set_alignment(0.0, 0.5)
+        self._arrTime.set_use_markup(True)
+        self._arrTime.set_tooltip_markup(xstr("finish_arr_time_tooltip"))
+        labelAlignment.add(self._arrTime)
+        table.attach(labelAlignment, 1, 2, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_distance"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        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, row, row+1)
+
+        row += 1
+
+        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
+        label = gtk.Label(xstr("finish_fuel"))
+        labelAlignment.add(label)
+        table.attach(labelAlignment, 0, 1, row, row+1)
+
+        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, row, row+1)
+
+        row += 1
+
+        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, row, row+1)
+
+        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, row, row+1)
+        label.set_mnemonic_widget(self._flightType)
+
+        row += 1
+
+        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, row, row + 1)
+
+        row += 1
+
+        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, row, row+1)
+
+        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, row, row+1)
+        self._gateLabel.set_mnemonic_widget(self._gate)
+
+        self.addButton(xstr("finish_newFlight"),
+                       sensitive = True,
+                       clicked = self._newFlightClicked,
+                       tooltip = xstr("finish_newFlight_tooltip"),
+                       padding = 16)
+
+        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._tooBigTimeDifference = False
+        self._deferredAutoSave = False
+        self._pirepSaved = False
+        self._pirepSent = False
+
+        self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
+                                          sensitive = False,
+                                          clicked = self._sendClicked,
+                                          tooltip = xstr("sendPIREP_tooltip"))
+
+        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 10*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 20*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 0*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), (23*60.0+50)*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (1*60.0+5)*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (0*60.0+50)*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (0*60.0+5)*60.0)
+        # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (23*60.0+45)*60.0)
+
+    @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."""
+        self._deferredAutoSave = False
+        self._pirepSaved = False
+        self._pirepSent = False
+
+        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(self._wizard.loggedIn)
+
+        self._gatesModel.clear()
+        if self._wizard.gui.config.onlineGateSystem and \
+           self._wizard.loggedIn and \
+           self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
+           not self._wizard.entranceExam:
+            occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
+            for gate in lhbpGates.gates:
+                if gate.isAvailable(lhbpGates, occupiedGateNumbers):
+                    self._gatesModel.append([gate.number])
+            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)
+
+        self._updateTimes()
+
+    def updateButtons(self):
+        """Update the sensitivity state of the buttons."""
+        gui = self._wizard.gui
+        faultsExplained = gui.faultsFullyExplained
+        timesCorrect = self.flightType is None or \
+                       not self._tooBigTimeDifference or \
+                       gui.hasComments or gui.hasDelayCode
+        sensitive = gui.flight is not None and \
+                    gui.flight.stage==const.STAGE_END and \
+                    self._flightType.get_active()>=0 and \
+                    (self._gatesModel.get_iter_first() is None or
+                     self._gate.get_active()>=0) and \
+                     faultsExplained and timesCorrect
+
+        self._updateHelp(faultsExplained, timesCorrect)
+
+        wasSensitive = self._saveButton.get_sensitive()
+
+        if gui.config.pirepAutoSave and sensitive and not wasSensitive:
+            if gui.isWizardActive():
+                self._autoSavePIREP()
+            else:
+                self._deferredAutoSave = True
+
+        if not sensitive:
+            self._deferredAutoSave = False
+
+        self._saveButton.set_sensitive(sensitive)
+        self._sendButton.set_sensitive(sensitive and
+                                       self._wizard.bookedFlight.id is not None)
+
+    def grabDefault(self):
+        """If the page has a default button, make it the default one."""
+        super(FinishPage, self).grabDefault()
+        if self._deferredAutoSave:
+            self._autoSavePIREP()
+            self._deferredAutoSave = False
+
+    def _autoSavePIREP(self):
+        """Perform the automatic saving of the PIREP."""
+        self._lastSavePath = os.path.join(self._wizard.gui.config.pirepDirectory,
+                                          self._getDefaultPIREPName())
+        self._lastSavePath = text2unicode(self._lastSavePath)
+        self._savePIREP(automatic = True)
+
+    def _backClicked(self, button):
+        """Called when the Back button is pressed."""
+        self.goBack()
+
+    def _flightTypeChanged(self, comboBox):
+        """Called when the flight type has changed."""
+        self._updateTimes()
+
+    def _gateChanged(self, comboBox):
+        """Called when the arrival gate has changed."""
+        self.updateButtons()
+
+    def _newFlightClicked(self, button):
+        """Called when the new flight button is clicked."""
+        gui = self._wizard.gui
+        if not self._pirepSent and not self._pirepSaved:
+            dialog = gtk.MessageDialog(parent = gui.mainWindow,
+                                       type = MESSAGETYPE_QUESTION,
+                                       message_format = xstr("finish_newFlight_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:
+                return
+
+        gui.reset()
+
+    def _getDefaultPIREPName(self):
+        """Get the default name of the PIREP."""
+        gui = self._wizard.gui
+
+        bookedFlight = gui.bookedFlight
+        tm = time.gmtime()
+
+        pilotID = self._wizard.pilotID
+        if pilotID: pilotID += " "
+        return "%s%s %02d%02d %s-%s.pirep" % \
+               (pilotID, str(bookedFlight.departureTime.date()),
+                tm.tm_hour, tm.tm_min,
+                bookedFlight.departureICAO, bookedFlight.arrivalICAO)
+
+
+    def _saveClicked(self, button):
+        """Called when the Save PIREP button is clicked."""
+        gui = self._wizard.gui
+
+        fileName = self._getDefaultPIREPName()
+
+        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:
+            self._lastSavePath = text2unicode(dialog.get_filename())
+            self._savePIREP()
+
+    def _savePIREP(self, automatic = False):
+        """Perform the saving of the PIREP."""
+
+        gui = self._wizard.gui
+
+        if automatic:
+            gui.beginBusy(xstr("finish_autosave_busy"))
+
+        pirep = PIREP(gui.flight)
+        error = pirep.save(self._lastSavePath)
+
+        if automatic:
+            gui.endBusy()
+
+        if error:
+            type = MESSAGETYPE_ERROR
+            message = xstr("finish_save_failed")
+            secondary = xstr("finish_save_failed_sec") % (text2unicode(error),)
+        else:
+            type = MESSAGETYPE_INFO
+            message = xstr("finish_save_done")
+            if automatic:
+                secondary = xstr("finish_save_done_sec") % (self._lastSavePath,)
+            else:
+                secondary = None
+            self._pirepSaved = True
+
+        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("file_filter_pireps"))
+            filter.add_pattern("*.pirep")
+            dialog.add_filter(filter)
+
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("file_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.flight)
+        self._wizard.gui.sendPIREP(pirep,
+                                   callback = self._handlePIREPSent)
+
+    def _handlePIREPSent(self, returned, result):
+        """Callback for the PIREP sending result."""
+        self._pirepSent = returned and result.success
+        if self._wizard.gui.config.onlineGateSystem and \
+           self._wizard.loggedIn and not self._wizard.entranceExam 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
+
+    def _formatTime(self, scheduledTime, realTimestamp, (warning, error)):
+        """Format the departure or arrival time based on the given data as a
+        markup for a label."""
+        realTime = time.gmtime(realTimestamp)
+
+        if warning:
+            colour = "red" if error else "orange"
+            markupBegin = '<span foreground="%s">' % (colour,)
+            markupEnd = '</span>'
+        else:
+            markupBegin = markupEnd = ""
+
+        markup = "<b>%s%02d:%02d [%02d:%02d]%s</b>" % \
+                 (markupBegin,
+                  realTime.tm_hour, realTime.tm_min,
+                  scheduledTime.hour, scheduledTime.minute,
+                  markupEnd)
+
+        return markup
+
+    def _updateTimes(self):
+        """Format the flight times and the help text according to the flight
+        type.
+
+        The buttons are also updated.
+        """
+        flight = self._wizard.gui._flight
+        bookedFlight = flight.bookedFlight
+
+        (departureWarning, departureError) = flight.blockTimeStartWrong
+        (arrivalWarning, arrivalError) = flight.blockTimeEndWrong
+
+        if self.flightType==const.FLIGHTTYPE_VIP:
+            departureError = arrivalError = False
+
+        self._tooBigTimeDifference = departureError or arrivalError
+
+        self._depTime.set_markup(self._formatTime(bookedFlight.departureTime,
+                                                  flight.blockTimeStart,
+                                                  (departureWarning,
+                                                   departureError)))
+
+        self._arrTime.set_markup(self._formatTime(bookedFlight.arrivalTime,
+                                                  flight.blockTimeEnd,
+                                                  (arrivalWarning,
+                                                   arrivalError)))
+
+        self.updateButtons()
+
+    def _updateHelp(self, faultsExplained, timesCorrect):
+        """Update the help text according to the actual situation."""
+        if not faultsExplained:
+            self.setHelp(xstr("finish_help") + xstr("finish_help_faults"))
+        elif not timesCorrect:
+            self.setHelp(xstr("finish_help") + xstr("finish_help_wrongtime"))
+        else:
+            self.setHelp(xstr("finish_help") + xstr("finish_help_goodtime"))
+
+
+#-----------------------------------------------------------------------------
+
+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._loginPage = LoginPage(self)
+        self._pages.append(self._loginPage)
+        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._routePage = RoutePage(self)
+        self._pages.append(self._routePage)
+        self._simBriefSetupPage = SimBriefSetupPage(self)
+        self._pages.append(self._simBriefSetupPage)
+        self._simBriefingPage = SimBriefingPage(self)
+        self._pages.append(self._simBriefingPage)
+        self._pages.append(FuelPage(self))
+        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._cruisePage = CruisePage(self)
+        self._pages.append(self._cruisePage)
+        self._landingPage = LandingPage(self)
+        self._pages.append(self._landingPage)
+        self._finishPage = FinishPage(self)
+        self._pages.append(self._finishPage)
+
+        self._requestedWidth = None
+        self._requestedHeight = None
+
+        self.connect("size-allocate", self._sizeAllocate)
+
+        for page in self._pages:
+            page.show_all()
+            page.setStyle()
+
+        self._initialize()
+
+    def _sizeAllocate(self, widget, allocation):
+        if self._requestedWidth is not None and \
+           self._requestedHeight is not None:
+           return
+
+        if self._currentPage is not None:
+            self.remove(self._pages[self._currentPage])
+
+        maxWidth = 0
+        maxHeight = 0
+        for page in self._pages:
+            self.add(page)
+            self.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)
+            self.remove(page)
+
+        if self._currentPage is not None:
+            self.add(self._pages[self._currentPage])
+
+        self._requestedWidth = maxWidth
+        self._requestedHeight = maxHeight
+        self.set_size_request(maxWidth, maxHeight)
+
+    @property
+    def pilotID(self):
+        """Get the pilot ID, if given."""
+        return self._loginPage.pilotID
+
+    @property
+    def entranceExam(self):
+        """Get whether an entrance exam is about to be taken."""
+        return self._loginPage.entranceExam
+
+    @property
+    def loggedIn(self):
+        """Indicate if there was a successful login."""
+        return self._loginResult is not None
+
+    @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 numCrew(self):
+        """Get the number of crew members."""
+        return self._payloadPage.numCrew
+
+    @property
+    def numPassengers(self):
+        """Get the number of passengers."""
+        return self._payloadPage.numPassengers
+
+    @property
+    def bagWeight(self):
+        """Get the baggage weight."""
+        return self._payloadPage.bagWeight
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight."""
+        return self._payloadPage.cargoWeight
+
+    @property
+    def mailWeight(self):
+        """Get the mail weight."""
+        return self._payloadPage.mailWeight
+
+    @property
+    def zfw(self):
+        """Get the calculated ZFW value."""
+        return 0 if self._bookedFlight is None \
+               else self._payloadPage.calculateZFW()
+
+    @property
+    def filedCruiseLevel(self):
+        """Get the filed cruise level."""
+        return self._routePage.filedCruiseLevel
+
+    @property
+    def filedCruiseAltitude(self):
+        """Get the filed cruise altitude."""
+        return self._routePage.filedCruiseLevel * 100
+
+    @property
+    def cruiseAltitude(self):
+        """Get the cruise altitude."""
+        level = self._cruisePage.cruiseLevel if self._cruisePage.activated \
+                else self._routePage.filedCruiseLevel
+        return level * 100
+
+    @property
+    def loggableCruiseAltitude(self):
+        """Get the cruise altitude that can be logged."""
+        if self._cruisePage.activated:
+            return self._cruisePage.loggableCruiseLevel * 100
+        else:
+            return 0
+
+    @property
+    def route(self):
+        """Get the route."""
+        return self._routePage.route
+
+    @property
+    def alternate(self):
+        """Get the ICAO code of the alternate airport."""
+        return self._routePage.alternate
+
+    @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 derate(self):
+        """Get the derate value."""
+        return self._takeoffPage.derate
+
+    @property
+    def takeoffAntiIceOn(self):
+        """Get whether the anti-ice system was on during take-off."""
+        return self._takeoffPage.antiIceOn
+
+    @takeoffAntiIceOn.setter
+    def takeoffAntiIceOn(self, value):
+        """Set anti-ice on indicator."""
+        self._takeoffPage.antiIceOn = value
+
+    @property
+    def rtoIndicated(self):
+        """Get whether the pilot has indicated that an RTO has occured."""
+        return self._takeoffPage.rtoIndicated
+
+    @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 landingAntiIceOn(self):
+        """Get whether the anti-ice system was on during landing."""
+        return self._landingPage.antiIceOn
+
+    @landingAntiIceOn.setter
+    def landingAntiIceOn(self, value):
+        """Set anti-ice on indicator."""
+        self._landingPage.antiIceOn = value
+
+    @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
+
+    @property
+    def usingSimBrief(self):
+        """Indicate if we are using a SimBrief briefing or not."""
+        return self._usingSimBrief
+
+    @usingSimBrief.setter
+    def usingSimBrief(self, x):
+        """Set whether we are using a SimBrief briefing or not."""
+        self._usingSimBrief = x
+
+    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, loginResult):
+        """Resets the wizard to go back to the login page."""
+        self._initialize(keepLoginResult = loginResult is None,
+                         loginResult = loginResult)
+
+    def setStage(self, stage):
+        """Set the flight stage to the given one."""
+        if stage!=const.STAGE_END:
+            self._cruisePage.setLoggable(Flight.canLogCruiseAltitude(stage))
+
+        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:
+            for page in self._pages:
+                page.flightEnded()
+
+    def _initialize(self, keepLoginResult = False, loginResult = None):
+        """Initialize the wizard."""
+        if not keepLoginResult:
+            self._loginResult = loginResult
+
+        self._loginCallback = None
+
+        self._fleet = None
+        self._fleetCallback = None
+
+        self._bookedFlight = None
+        self._departureGate = "-"
+        self._fuelData = None
+        self._departureNOTAMs = None
+        self._departureMETAR = None
+        self._arrivalNOTAMs = None
+        self._arrivalMETAR = None
+        self._usingSimBrief = False
+
+        firstPage = 0 if self._loginResult is None else 1
+        for page in self._pages[firstPage:]:
+            page.reset()
+
+        self.setCurrentPage(firstPage)
+        #self.setCurrentPage(10)
+
+    def login(self, callback, pilotID, password, entranceExam):
+        """Called when the login button was clicked."""
+        self._loginCallback = callback
+        if pilotID is None:
+            loginResult = self._loginResult
+            assert loginResult is not None and loginResult.loggedIn
+            pilotID = loginResult.pilotID
+            password = loginResult.password
+            entranceExam = loginResult.entranceExam
+            busyMessage = xstr("reload_busy")
+        else:
+            self._loginResult = None
+            busyMessage = xstr("login_busy")
+
+        self.gui.beginBusy(busyMessage)
+
+        self.gui.webHandler.login(self._loginResultCallback,
+                                  pilotID, password,
+                                  entranceExam = entranceExam)
+
+    def reloadFlights(self, callback):
+        """Reload the flights from the MAVA server."""
+        self.login(callback, None, None, None)
+
+    def cruiseLevelChanged(self):
+        """Called when the cruise level is changed."""
+        return self.gui.cruiseLevelChanged()
+
+    def metarChanged(self, metar, originator):
+        """Called when a METER is changed on on of the pages.
+
+        originator is the page that originated the changed. It will be used to
+        determine which METAR (departure or arrival) has changed."""
+        metar = metar.upper()
+        if originator in [self._departureBriefingPage, self._takeoffPage]:
+            self._departureMETARChanged(metar, originator)
+        else:
+            self._arrivalMETARChanged(metar, originator)
+
+    def _departureMETARChanged(self, metar, originator):
+        """Called when the departure METAR has been edited on one of the
+        pages.
+
+        originator is the page that originated the change. It will not be
+        called to set the METAR, while others will be."""
+        for page in [self._departureBriefingPage, self._takeoffPage]:
+            if page is not originator:
+                page.changeMETAR(metar)
+
+    def _arrivalMETARChanged(self, metar, originator):
+        """Called when the arrival METAR has been edited on one of the
+        pages.
+
+        originator is the page that originated the change. It will not be
+        called to set the METAR, while others will be."""
+        for page in [self._arrivalBriefingPage, self._landingPage]:
+            if page is not originator:
+                page.changeMETAR(metar)
+
+    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.gui.endBusy()
+        returned = True
+        isReload = self._loginResult is not None
+        if returned:
+            if result.loggedIn:
+                self._loginResult = result
+            else:
+                if isReload:
+                    message = xstr("reload_failed")
+                else:
+                    message = xstr("login_entranceExam_invalid"
+                                   if self.entranceExam else
+                                   xstr("login_invalid"))
+                dialog = gtk.MessageDialog(parent = self.gui.mainWindow,
+                                           type = MESSAGETYPE_ERROR,
+                                           message_format = message)
+                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+                dialog.set_title(WINDOW_TITLE_BASE)
+                if isReload:
+                    secondary = xstr("reload_failed_sec")
+                else:
+                    secondary = xstr("login_entranceExam_invalid_sec"
+                                     if self.entranceExam else
+                                     xstr("login_invalid_sec"))
+                dialog.format_secondary_markup(secondary)
+                dialog.run()
+                dialog.hide()
+        else:
+            message = xstr("reload_failconn") if isReload \
+                      else xstr("login_failconn")
+            dialog = gtk.MessageDialog(parent = self.gui.mainWindow,
+                                       type = MESSAGETYPE_ERROR,
+                                       message_format = message)
+            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+            dialog.set_title(WINDOW_TITLE_BASE)
+            secondary = xstr("reload_failconn_sec") if isReload \
+                        else xstr("login_failconn_sec")
+            dialog.format_secondary_markup(secondary)
+
+            dialog.run()
+            dialog.hide()
+
+        callback = self._loginCallback
+        self._loginCallback = None
+        callback(returned, result)
+
+    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 updateRTO(self):
+        """Update the RTO state.
+
+        The RTO checkbox will be enabled if the flight has an RTO state and the
+        comments field contains some text."""
+        flight = self.gui.flight
+        rtoEnabled = flight is not None and flight.hasRTO and \
+                     self.gui.hasComments
+        self._takeoffPage.setRTOEnabled(rtoEnabled)
+
+    def commentsChanged(self):
+        """Called when the comments have changed."""
+        self.updateRTO()
+        self._finishPage.updateButtons()
+
+    def delayCodesChanged(self):
+        """Called when the delay codes have changed."""
+        self._finishPage.updateButtons()
+
+    def faultExplanationsChanged(self):
+        """Called when the faults and their explanations have changed."""
+        self._finishPage.updateButtons()
+
+    def rtoToggled(self, indicated):
+        """Called when the RTO indication has changed."""
+        self.gui.rtoToggled(indicated)
+
+    def _connectSimulator(self, simulatorType):
+        """Connect to the simulator."""
+        self.gui.connectSimulator(self._bookedFlight.aircraftType,
+                                  simulatorType)
+
+    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 687)
+++ 	(revision )
@@ -1,4372 +1,0 @@
-
-from mlx.gui.common import *
-import mlx.gui.cef as cef
-
-import mlx.const as const
-import mlx.fs as fs
-import mlx.acft as acft
-from mlx.flight import Flight
-from mlx.checks import PayloadChecker
-from mlx.gates import lhbpGates
-import mlx.util as util
-from mlx.pirep import PIREP
-from mlx.i18n import xstr
-from mlx.sound import startSound
-import mlx.web as web
-
-import datetime
-import time
-import os
-import tempfile
-
-#-----------------------------------------------------------------------------
-
-## @package mlx.gui.flight
-#
-# The flight "wizard".
-#
-# This module implements the main tab of the application, the flight
-# wizard. The wizard consists of \ref Page "pages", that come one after the
-# other. As some pages might be skipped, the pages dynamically store the index
-# of the previous page so that going back to it is simpler. The \ref
-# Page.activate "activate" function is called before a page is first shown
-# during a flight. This function should initialize the page's controls and fill
-# it with initial data. When a page is left for the first time, its \ref
-# Page.finalize "finalize" function is called. It should set those controls
-# insensitive, that will not be available if the user comes back to this page.
-#
-# Each page has a title at the top displayed in inverted colors and a big
-# font. There is a help text below it centered, that shortly describes what is
-# expected on the page. There can be two help texts: one shown when the page is
-# first displayed during a flight, another shown when the user goes back to the
-# page. The main content area is below this, also centered. Finally, there are
-# some buttons at the bottom on the right. As some buttons occur very
-# frequently, there are functions to add them (\ref Page.addCancelFlightButton
-# "addCancelFlightButton", \ref Page.addPreviousButton "addPreviousButton" and
-# \ref Page.addNextButton "addNextButton".
-#
-# The \ref Wizard class is the main class to collect the pages. It also stores
-# some data passed from one page to another and provides properties to access
-# data items set via the wizard pages.
-
-#-----------------------------------------------------------------------------
-
-comboModel = gtk.ListStore(gobject.TYPE_STRING)
-comboModel.append(("N/A",))
-comboModel.append(("VECTORS",))
-
-#-----------------------------------------------------------------------------
-
-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)
-
-        self._vbox = gtk.VBox()
-        self._vbox.set_homogeneous(False)
-        frame.add(self._vbox)
-
-        eventBox = gtk.EventBox()
-
-        alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
-
-        titleLabel = gtk.Label(title)
-        titleLabel.modify_font(pango.FontDescription("bold 24"))
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 6, padding_right = 0)
-
-        alignment.add(titleLabel)
-        eventBox.add(alignment)
-
-        self._vbox.pack_start(eventBox, False, False, 0)
-
-        self._titleEventBox = eventBox
-        self._titleLabel = titleLabel
-
-        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(longerHelp)
-        # 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._cancelFlightButton = None
-
-        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, padding = 4):
-        """Add a button with the given label.
-
-        Return the button object created."""
-        button = gtk.Button(label)
-        self._buttonBox.pack_start(button, False, False, padding)
-        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 addCancelFlightButton(self):
-        """Add the 'Cancel flight' button to the page."""
-        self._cancelFlightButton = \
-            self.addButton(xstr("button_cancelFlight"),
-                           sensitive = True,
-                           tooltip = xstr("button_cancelFlight_tooltip"),
-                           clicked = self._cancelFlight,
-                           padding = 16)
-        return self._cancelFlightButton
-
-    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 setStyle(self):
-        """Set the styles of some of the items on the page."""
-        if pygobject:
-            context = self.get_style_context()
-            color = context.get_background_color(gtk.StateFlags.SELECTED)
-            self._titleEventBox.modify_bg(0, color.to_color())
-            color = context.get_color(gtk.StateFlags.SELECTED)
-            self._titleLabel.modify_fg(0, color.to_color())
-        else:
-            style = self.rc_get_style()
-            self._titleEventBox.modify_bg(0, style.bg[3])
-            self._titleLabel.modify_fg(0, style.fg[3])
-
-    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 setHelp(self, help):
-        """Set the help string."""
-        self._help = help
-        if not self._completed:
-            self._helpLabel.set_markup(self._help)
-            self._helpLabel.set_sensitive(True)
-
-    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
-        if self._cancelFlightButton is not None:
-            self._cancelFlightButton.set_sensitive(True)
-
-    def goBack(self):
-        """Go to the page we were invoked from."""
-        assert self._fromPage is not None
-
-        self._wizard.setCurrentPage(self._fromPage, finalize = False)
-
-    def flightEnded(self):
-        """Called when the flight has ended.
-
-        This default implementation disables the cancel flight button."""
-        if self._cancelFlightButton is not None:
-            self._cancelFlightButton.set_sensitive(False)
-
-    def _cancelFlight(self, button):
-        """Called when the Cancel flight button is clicked."""
-        self._wizard.gui.cancelFlight()
-
-#-----------------------------------------------------------------------------
-
-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(4, 2)
-        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._pilotIDChanged)
-        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._setControls)
-        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._entranceExam = gtk.CheckButton(xstr("login_entranceExam"))
-        self._entranceExam.set_use_underline(True)
-        self._entranceExam.set_tooltip_text(xstr("login_entranceExam_tooltip"))
-        self._entranceExam.connect("toggled", self._setControls)
-        table.attach(self._entranceExam, 1, 2, 3, 4, ypadding = 12)
-
-        self.addButton(xstr("button_offline"),
-                       clicked = self._offlineClicked,
-                       tooltip = xstr("button_offline_tooltip"))
-
-        self._loginButton = self.addButton(xstr("button_login"), default = True)
-        self._loginButton.connect("clicked", self._loginClicked)
-        self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))
-
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is being performed."""
-        return self._entranceExam.get_active() and \
-               self._pilotID.get_text()!=""
-
-    @property
-    def pilotID(self):
-        """Get the pilot ID, if given."""
-        return self._pilotID.get_text()
-
-    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)
-        self._setControls(None)
-
-    def _pilotIDChanged(self, entry):
-        """Called when the pilot ID has changed.
-
-        It sets the text to upper-case and calls _setControls to update other
-        stuff."""
-        entry.set_text(entry.get_text().upper())
-        self._setControls(entry)
-
-    def _setControls(self, entry = None):
-        """Set the sensitivity of the various controls.
-
-        The login button is sensitive only if both the pilot ID and the
-        password fields contain values.
-
-        The password field is sensitive only, if the entrance exam checkbox is
-        not selected.
-
-        The remember password checkbox is sensitive only, if the password field
-        contains text.
-
-        The entrance exam checkbox is sensitive only, if the pilot ID is not
-        empty."""
-        pilotID = self._pilotID.get_text()
-        password = self._password.get_text()
-        entranceExam = self._entranceExam.get_active()
-        self._password.set_sensitive(not entranceExam)
-        self._rememberButton.set_sensitive(password!="" and not entranceExam)
-        self._entranceExam.set_sensitive(pilotID!="")
-        self._loginButton.set_sensitive(pilotID!="" and
-                                        (password!="" or entranceExam))
-
-    def _offlineClicked(self, button):
-        """Called when the offline button was clicked."""
-        print "mlx.flight.LoginPage: offline flight selected"
-        self._wizard.nextPage()
-
-    def _loginClicked(self, button):
-        """Called when the login button was clicked."""
-        print "mlx.flight.LoginPage: logging in"
-        self._wizard.login(self._handleLoginResult,
-                           self._pilotID.get_text(),
-                           self._password.get_text(),
-                           self.entranceExam)
-
-    def _handleLoginResult(self, returned, result):
-        """Handle the login result."""
-        self._loginButton.set_sensitive(True)
-        if returned and result.loggedIn:
-            config = self._wizard.gui.config
-
-            config.pilotID = self._pilotID.get_text()
-
-            rememberPassword = self._rememberButton.get_active()
-            config.password = result.password if rememberPassword else ""
-
-            config.rememberPassword = rememberPassword
-
-            config.save()
-            self._wizard.nextPage()
-
-#-----------------------------------------------------------------------------
-
-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)
-        self._flightList.connect("row-activated", self._rowActivated)
-        self._flightList.connect("button-press-event", self._listButtonPressed)
-
-        self._flightListPopupMenu = None
-
-        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._saveButton = self.addButton(xstr("flightsel_save"),
-                                          sensitive = False,
-                                          clicked = self._saveClicked,
-                                          tooltip = xstr("flightsel_save_tooltip"))
-        self._saveDialog = None
-
-        self._refreshButton = self.addButton(xstr("flightsel_refresh"),
-                                             sensitive = True,
-                                             clicked = self._refreshClicked,
-                                             tooltip = xstr("flightsel_refresh_tooltip"))
-
-        self._loadButton = self.addButton(xstr("flightsel_load"),
-                                          sensitive = True,
-                                          tooltip = xstr("flightsel_load_tooltip"))
-        self._loadButton.connect("clicked", self._loadButtonClicked)
-        self._loadDialog = None
-
-        self._button = self.addNextButton(sensitive = False,
-                                          clicked =  self._forwardClicked)
-
-        self._flights = []
-
-    def activate(self):
-        """Fill the flight list."""
-        self._flightList.set_sensitive(True)
-        self._loadButton.set_sensitive(True)
-        self._refreshButton.set_sensitive(self._wizard.loggedIn)
-        self._buildFlights()
-
-    def finalize(self):
-        """Finalize the page."""
-        self._flightList.set_sensitive(False)
-        self._loadButton.set_sensitive(False)
-        self._refreshButton.set_sensitive(False)
-
-    def _buildFlights(self):
-        """Rebuild the flights from the login result."""
-        self._flights = []
-        self._listStore.clear()
-        if self._wizard.loggedIn:
-            for flight in self._wizard.loginResult.flights:
-                self._addFlight(flight)
-
-    def _addFlight(self, flight):
-        """Add the given file to the list of flights."""
-        self._flights.append(flight)
-        self._listStore.append([str(flight.departureTime),
-                                flight.callsign,
-                                flight.departureICAO,
-                                flight.arrivalICAO])
-
-    def _saveClicked(self, button):
-        """Called when the Save flight button is clicked."""
-        self._saveSelected()
-
-    def _saveSelected(self):
-        """Save the selected flight."""
-        flight = self._getSelectedFlight()
-        date = flight.departureTime.date()
-        name = "%04d-%02d-%02d %s %s-%s.vaflight" % \
-               (date.year, date.month, date.day, flight.callsign,
-                flight.departureICAO, flight.arrivalICAO)
-
-        dialog = self._getSaveDialog()
-        dialog.set_current_name(name)
-        dialog.show_all()
-        response = dialog.run()
-        dialog.hide()
-
-        if response==RESPONSETYPE_OK:
-            fileName = text2unicode(dialog.get_filename())
-            print "Saving", fileName
-            try:
-                with open(fileName, "wt") as f:
-                    flight.writeIntoFile(f)
-            except Exception, e:
-                print "Failed to save flight:", util.utf2unicode(str(e))
-                dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
-                                           type = MESSAGETYPE_ERROR,
-                                           message_format =
-                                           xstr("flightsel_save_failed"))
-                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-                dialog.set_title(WINDOW_TITLE_BASE)
-                secondary = xstr("flightsel_save_failed_sec")
-                dialog.format_secondary_markup(secondary)
-                dialog.run()
-                dialog.hide()
-
-    def _refreshClicked(self, button):
-        """Called when the refresh button is clicked."""
-        self._wizard.reloadFlights(self._refreshCallback)
-
-    def _refreshCallback(self, returned, result):
-        """Callback for the refresh."""
-        if returned and result.loggedIn:
-            self._buildFlights()
-
-    def _selectionChanged(self, selection):
-        """Called when the selection is changed."""
-        selected = selection.count_selected_rows()==1
-        self._saveButton.set_sensitive(selected)
-        self._button.set_sensitive(selected)
-
-    def _loadButtonClicked(self, loadButton):
-        """Called when the load a flight button is clicked."""
-        dialog = self._getLoadDialog()
-        dialog.show_all()
-        response = dialog.run()
-        dialog.hide()
-
-        if response==RESPONSETYPE_OK:
-            fileName = text2unicode(dialog.get_filename())
-            print "Loading", fileName
-            bookedFlight = web.BookedFlight()
-            try:
-                with open(fileName, "rt") as f:
-                    bookedFlight.readFromFile(f)
-                self._addFlight(bookedFlight)
-            except Exception, e:
-                print "Failed to load flight:", util.utf2unicode(str(e))
-                dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
-                                           type = MESSAGETYPE_ERROR,
-                                           message_format =
-                                           xstr("flightsel_load_failed"))
-                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-                dialog.set_title(WINDOW_TITLE_BASE)
-                secondary = xstr("flightsel_load_failed_sec")
-                dialog.format_secondary_markup(secondary)
-                dialog.run()
-                dialog.hide()
-
-    def _forwardClicked(self, button):
-        """Called when the forward button was clicked."""
-        if self._completed:
-            self._wizard.jumpPage(self._nextDistance, finalize = False)
-        else:
-            self._flightSelected()
-
-    def _rowActivated(self, flightList, path, column):
-        """Called when a row is activated."""
-        if not self._completed:
-            self._flightSelected()
-
-    def _flightSelected(self):
-        """Called when a flight has been selected."""
-        flight = self._getSelectedFlight()
-        self._wizard._bookedFlight = flight
-        self._wizard.gui.enableFlightInfo(flight.aircraftType)
-
-        self._updateDepartureGate()
-
-    def _getSelectedFlight(self):
-        """Get the currently selected flight."""
-        selection = self._flightList.get_selection()
-        (listStore, iter) = selection.get_selected()
-        path = listStore.get_path(iter)
-        [index] = path.get_indices() if pygobject else path
-
-        return self._flights[index]
-
-    def _listButtonPressed(self, widget, event):
-        """Called when a mouse button is pressed on the flight list."""
-        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
-            return
-
-        (path, _, _, _) = self._flightList.get_path_at_pos(int(event.x),
-                                                           int(event.y))
-        selection = self._flightList.get_selection()
-        selection.unselect_all()
-        selection.select_path(path)
-
-        menu = self._getListPopupMenu()
-        if pygobject:
-            menu.popup(None, None, None, None, event.button, event.time)
-        else:
-            menu.popup(None, None, None, event.button, event.time)
-
-    def _updateDepartureGate(self):
-        """Update the departure gate for the booked flight."""
-        flight = self._wizard._bookedFlight
-        if self._wizard.gui.config.onlineGateSystem and \
-           self._wizard.loggedIn and not self._wizard.entranceExam:
-            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)
-
-    def _getSaveDialog(self):
-        """Get the dialog to load a flight file."""
-        if self._saveDialog is not None:
-            return self._saveDialog
-
-        gui = self._wizard.gui
-        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                       xstr("flightsel_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("flightsel_filter_flights"))
-        filter.add_pattern("*.vaflight")
-        dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name(xstr("file_filter_all"))
-        filter.add_pattern("*.*")
-        dialog.add_filter(filter)
-
-        self._saveDialog = dialog
-
-        return dialog
-
-    def _getLoadDialog(self):
-        """Get the dialog to load a flight file."""
-        if self._loadDialog is not None:
-            return self._loadDialog
-
-        gui = self._wizard.gui
-        dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
-                                       xstr("flightsel_load_title"),
-                                       action = FILE_CHOOSER_ACTION_OPEN,
-                                       buttons = (gtk.STOCK_CANCEL,
-                                                  RESPONSETYPE_CANCEL,
-                                                  gtk.STOCK_OK, RESPONSETYPE_OK),
-                                       parent = gui.mainWindow)
-        dialog.set_modal(True)
-
-        filter = gtk.FileFilter()
-        filter.set_name(xstr("flightsel_filter_flights"))
-        filter.add_pattern("*.vaflight")
-        dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name(xstr("file_filter_all"))
-        filter.add_pattern("*.*")
-        dialog.add_filter(filter)
-
-        self._loadDialog = dialog
-
-        return dialog
-
-    def _getListPopupMenu(self):
-        """Get the flight list popup menu."""
-        if self._flightListPopupMenu is None:
-            menu = gtk.Menu()
-
-            menuItem = gtk.MenuItem()
-            menuItem.set_label(xstr("flightsel_popup_select"))
-            menuItem.set_use_underline(True)
-            menuItem.connect("activate", self._popupSelect)
-            menuItem.show()
-
-            menu.append(menuItem)
-
-            menuItem = gtk.MenuItem()
-            menuItem.set_label(xstr("flightsel_popup_save"))
-            menuItem.set_use_underline(True)
-            menuItem.connect("activate", self._popupSave)
-            menuItem.show()
-
-            menu.append(menuItem)
-
-            self._flightListPopupMenu = menu
-
-        return self._flightListPopupMenu
-
-    def _popupSelect(self, menuItem):
-        """Called when the Select menu item is activated in the popup menu."""
-        if not self._completed:
-            self._flightSelected()
-
-    def _popupSave(self, menuItem):
-        """Called when the Save menu item is activated in the popup menu."""
-        if not self._completed:
-            self._saveSelected()
-
-#-----------------------------------------------------------------------------
-
-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)
-        self._gateList.connect("row-activated", self._rowActivated)
-
-        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.addCancelFlightButton()
-
-        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 gate in lhbpGates.gates:
-            if gate.isAvailable(lhbpGates, occupiedGateNumbers):
-                self._listStore.append([gate.number])
-
-    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:
-            self._gateSelected()
-        else:
-            self._wizard.nextPage()
-
-    def _rowActivated(self, flightList, path, column):
-        """Called when a row is activated."""
-        if not self._completed:
-            self._gateSelected()
-
-    def _gateSelected(self):
-        """Called when a gate has been selected."""
-        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)
-
-    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"))
-
-        self._selectSimulator = os.name=="nt" or "FORCE_SELECT_SIM" in os.environ
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(7 if self._selectSimulator else 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(9)
-        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)
-
-        if self._selectSimulator:
-            labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0, yalign=0.5)
-            label = gtk.Label(xstr("connect_sim"))
-            labelAlignment.add(label)
-            table.attach(labelAlignment, 0, 1, 5, 7)
-
-            selectAlignment = gtk.Alignment(xalign=0.0, xscale=0.0, yalign=0.5)
-
-            selectBox = gtk.HBox()
-            if pygobject:
-                self._selectMSFS = \
-                  gtk.RadioButton.new_with_mnemonic_from_widget(None,
-                                                                xstr("connect_sim_msfs"))
-            else:
-                self._selectMSFS = gtk.RadioButton(None,
-                                                   xstr("connect_sim_msfs"))
-
-            selectBox.pack_start(self._selectMSFS, False, False, 0);
-
-            if pygobject:
-                self._selectXPlane = \
-                  gtk.RadioButton.new_with_mnemonic_from_widget(self._selectMSFS,
-                                                                xstr("connect_sim_xplane"))
-            else:
-                self._selectXPlane = gtk.RadioButton(self._selectMSFS,
-                                                     xstr("connect_sim_xplane"))
-
-            selectBox.pack_start(self._selectXPlane, False, False, 8);
-
-            selectAlignment.add(selectBox)
-            table.attach(selectAlignment, 1, 2, 5, 7)
-
-
-        self.addCancelFlightButton()
-
-        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 = aircraftNames[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)
-
-        if self._selectSimulator:
-            config = self._wizard.gui.config
-            self._selectMSFS.set_active(config.defaultMSFS)
-            self._selectXPlane.set_active(not config.defaultMSFS)
-
-    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."""
-        if self._selectSimulator:
-            simulatorType = const.SIM_MSFS9 if self._selectMSFS.get_active() \
-                                            else const.SIM_XPLANE10
-        else:
-            simulatorType = const.SIM_MSFS9 if os.name=="nt" \
-              else const.SIM_XPLANE10
-
-        config = self._wizard.gui.config
-        config.defaultMSFS = simulatorType == const.SIM_MSFS9
-        config.save()
-
-        self._wizard._connectSimulator(simulatorType)
-
-    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_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 0, 1)
-
-        self._numCrew = IntegerEntry(defaultValue = 0)
-        self._numCrew.set_width_chars(6)
-        self._numCrew.connect("integer-changed", self._weightChanged)
-        self._numCrew.set_tooltip_text(xstr("payload_crew_tooltip"))
-        table.attach(self._numCrew, 1, 2, 0, 1)
-        label.set_mnemonic_widget(self._numCrew)
-
-        label = gtk.Label(xstr("payload_pax"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 1, 2)
-
-        self._numPassengers = IntegerEntry(defaultValue = 0)
-        self._numPassengers.set_width_chars(6)
-        self._numPassengers.connect("integer-changed", self._weightChanged)
-        self._numPassengers.set_tooltip_text(xstr("payload_pax_tooltip"))
-        table.attach(self._numPassengers, 1, 2, 1, 2)
-        label.set_mnemonic_widget(self._numPassengers)
-
-        label = gtk.Label(xstr("payload_bag"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 2, 3)
-
-        self._bagWeight = IntegerEntry(defaultValue = 0)
-        self._bagWeight.set_width_chars(6)
-        self._bagWeight.connect("integer-changed", self._weightChanged)
-        self._bagWeight.set_tooltip_text(xstr("payload_bag_tooltip"))
-        table.attach(self._bagWeight, 1, 2, 2, 3)
-        label.set_mnemonic_widget(self._bagWeight)
-
-        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._weightChanged)
-        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_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 4, 5)
-
-        self._mailWeight = IntegerEntry(defaultValue = 0)
-        self._mailWeight.set_width_chars(6)
-        self._mailWeight.connect("integer-changed", self._weightChanged)
-        self._mailWeight.set_tooltip_text(xstr("payload_mail_tooltip"))
-        table.attach(self._mailWeight, 1, 2, 4, 5)
-        label.set_mnemonic_widget(self._mailWeight)
-
-        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.addCancelFlightButton()
-        self._backButton = self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def numCrew(self):
-        """The number of the crew members on the flight."""
-        return self._numCrew.get_int()
-
-    @property
-    def numPassengers(self):
-        """The number of the passengers on the flight."""
-        return self._numPassengers.get_int()
-
-    @property
-    def bagWeight(self):
-        """Get the bag weight entered."""
-        return self._bagWeight.get_int()
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight entered."""
-        return self._cargoWeight.get_int()
-
-    @property
-    def mailWeight(self):
-        """Get the bag weight entered."""
-        return self._mailWeight.get_int()
-
-    def activate(self):
-        """Setup the information."""
-        bookedFlight = self._wizard._bookedFlight
-
-        self._numCrew.set_int(bookedFlight.numCrew)
-        self._numCrew.set_sensitive(True)
-        self._numPassengers.set_int(bookedFlight.numPassengers)
-        self._numPassengers.set_sensitive(True)
-
-        self._bagWeight.set_int(bookedFlight.bagWeight)
-        self._bagWeight.set_sensitive(True)
-        self._cargoWeight.set_int(bookedFlight.cargoWeight)
-        self._cargoWeight.set_sensitive(True)
-        self._mailWeight.set_int(bookedFlight.mailWeight)
-        self._mailWeight.set_sensitive(True)
-
-        self._simulatorZFW.set_text("-")
-        self._simulatorZFWValue = None
-        self._zfwButton.set_sensitive(True)
-        self._updateCalculatedZFW()
-
-    def finalize(self):
-        """Finalize the payload page."""
-        self._numCrew.set_sensitive(False)
-        self._numPassengers.set_sensitive(False)
-        self._bagWeight.set_sensitive(False)
-        self._cargoWeight.set_sensitive(False)
-        self._mailWeight.set_sensitive(False)
-        self._wizard.gui.initializeWeightHelp()
-
-    def calculateZFW(self):
-        """Calculate the ZFW value."""
-        zfw = self._wizard.gui._flight.aircraft.dow
-        zfw += (self._numCrew.get_int() + self._numPassengers.get_int()) * 82
-        zfw += self._bagWeight.get_int()
-        zfw += self._cargoWeight.get_int()
-        zfw += self._mailWeight.get_int()
-        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 _weightChanged(self, entry, weight):
-        """Called when one of the weight values or humanm counts 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.addCancelFlightButton()
-
-        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(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 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 = 0, max = 500)
-        self._cruiseLevel.set_tooltip_text(xstr("route_level_tooltip"))
-        self._cruiseLevel.set_numeric(True)
-        self._cruiseLevel.connect("changed", self._cruiseLevelChanged)
-        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
-        label.set_mnemonic_widget(self._cruiseLevel)
-
-        levelBox.pack_start(self._cruiseLevel, False, False, 8)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(levelBox)
-
-        mainBox.pack_start(alignment, False, False, 0)
-
-
-        routeBox = gtk.VBox()
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        label = gtk.Label(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)
-
-        alternateBox = gtk.HBox()
-
-        label = gtk.Label(xstr("route_altn"))
-        label.set_use_underline(True)
-        alternateBox.pack_start(label, True, True, 0)
-
-        self._alternate = gtk.Entry()
-        self._alternate.set_width_chars(6)
-        self._alternate.connect("changed", self._alternateChanged)
-        self._alternate.set_tooltip_text(xstr("route_altn_tooltip"))
-        label.set_mnemonic_widget(self._alternate)
-
-        alternateBox.pack_start(self._alternate, False, False, 8)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-        alignment.add(alternateBox)
-
-        mainBox.pack_start(alignment, False, False, 0)
-
-        self.addCancelFlightButton()
-
-        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._cruiseLevel.get_value_as_int()
-
-    @property
-    def route(self):
-        """Get the route."""
-        return self._getRoute()
-
-    @property
-    def alternate(self):
-        """Get the ICAO code of the alternate airport."""
-        return self._alternate.get_text()
-
-    def activate(self):
-        """Setup the route from the booked flight."""
-        self._cruiseLevel.set_value(0)
-        self._cruiseLevel.set_text("")
-        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."""
-        cruiseLevelText = self._cruiseLevel.get_text()
-        cruiseLevel = int(cruiseLevelText) if cruiseLevelText else 0
-        alternate = self._alternate.get_text()
-        self._button.set_sensitive(cruiseLevel>=50 and self._getRoute()!="" and
-                                   len(alternate)==4)
-
-    def _cruiseLevelChanged(self, *arg):
-        """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 _alternateChanged(self, entry):
-        """Called when the alternate airport has changed."""
-        entry.set_text(entry.get_text().upper())
-        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.useSimBrief:
-            self._wizard.nextPage()
-        else:
-            self._wizard.jumpPage(3)
-
-#-----------------------------------------------------------------------------
-
-class SimBriefSetupPage(Page):
-    """Page for setting up some parameters for SimBrief."""
-    monthNum2Name = [
-        "JAN",
-        "FEB",
-        "MAR",
-        "APR",
-        "MAY",
-        "JUN",
-        "JUL",
-        "AUG",
-        "SEP",
-        "OCT",
-        "NOV",
-        "DEC"
-        ]
-
-    @staticmethod
-    def getHTMLFilePath():
-        """Get the path of the HTML file to contain the generated flight
-        plan."""
-        if os.name=="nt":
-            return os.path.join(tempfile.gettempdir(),
-                                "mlx_simbrief" +
-                                (".secondary" if secondaryInstallation else "") +
-                                ".html")
-        else:
-            import pwd
-            return os.path.join(tempfile.gettempdir(),
-                                "mlx_simbrief." + pwd.getpwuid(os.getuid())[0] + "" +
-                                (".secondary" if secondaryInstallation else "") +
-                                ".html")
-
-    def __init__(self, wizard):
-        """Construct the setup page."""
-
-        super(SimBriefSetupPage, self).__init__(wizard,
-                                                "SimBrief setup",
-                                                "Complete the following data to "
-                                                "start generating your "
-                                                "SimBrief briefing.",
-                                                "Your SimBrief briefing was "
-                                                "generated with the following "
-                                                "data.")
-
-        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("_Username:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 0, 1)
-
-        self._userName = gtk.Entry()
-        self._userName.set_width_chars(16)
-        self._userName.connect("changed",
-                               lambda button: self._updateForwardButton())
-        self._userName.set_tooltip_text("Your SimBrief username")
-        table.attach(self._userName, 1, 2, 0, 1)
-        label.set_mnemonic_widget(self._userName)
-
-        label = gtk.Label("_Password:")
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, 1, 2)
-
-        self._password = gtk.Entry()
-        self._password.set_visibility(False)
-        self._password.connect("changed",
-                               lambda button: self._updateForwardButton())
-        self._password.set_tooltip_text("Your SimBrief password")
-        table.attach(self._password, 1, 2, 1, 2)
-        label.set_mnemonic_widget(self._password)
-
-        self.addCancelFlightButton()
-
-        self._backButton = self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    def activate(self):
-        """Activate the SimBrief setup page"""
-        self._updateForwardButton()
-
-    def _updateForwardButton(self):
-        """Update the sensitivity of the forward button."""
-        self._button.set_sensitive(len(self._userName.get_text())>0 and
-                                   len(self._password.get_text())>0)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _forwardClicked(self, button):
-        if self._completed:
-            self._wizard.nextPage()
-        else:
-            plan = self._getPlan()
-            print "plan:", plan
-
-            userName = self._userName.get_text()
-            password = self._password.get_text()
-
-            self._wizard.gui.beginBusy("Calling SimBrief...")
-
-            cef.callSimBrief(plan,
-                             lambda count: (userName, password),
-                             self._simBriefProgressCallback,
-                             SimBriefSetupPage.getHTMLFilePath())
-
-            startSound(const.SOUND_NOTAM)
-
-    def _simBriefProgressCallback(self, message, done):
-        """Called by the SimBrief handling thread."""
-        gobject.idle_add(self._simBriefProgress, message, done)
-
-    def _simBriefProgress(self, message, done):
-        """The real SimBrief progress handler."""
-        if done:
-            self._wizard.gui.endBusy()
-            self._wizard.nextPage()
-        else:
-            self._wizard.gui.updateBusyState(message)
-
-    def _getPlan(self):
-        """Get the flight plan data for SimBrief."""
-        plan = {
-            "airline": "MAH",
-            "selcal": "XXXX",
-            "fuelfactor": "P000",
-            "contpct": "0.05",
-            "resvrule": "45",
-            "taxiout": "10",
-            "taxiin": "10",
-            "civalue": "AUTO"
-            }
-
-        wizard = self._wizard
-        gui = wizard.gui
-
-        loginResult = wizard.loginResult
-        plan["cpt"] = loginResult.pilotName
-        plan["pid"] = loginResult.pilotID
-
-        bookedFlight = wizard.bookedFlight
-        plan["fltnum"] = wizard.bookedFlight.callsign[2:]
-        plan["type"] = const.icaoCodes[bookedFlight.aircraftType]
-        plan["orig"] = bookedFlight.departureICAO
-        plan["dest"] = bookedFlight.arrivalICAO
-        plan["reg"] = bookedFlight.tailNumber
-        plan["fin"] = bookedFlight.tailNumber[3:]
-        plan["pax"] = str(bookedFlight.numPassengers)
-
-        departureTime = bookedFlight.departureTime
-        plan["date"] = "%d%s%d" % (departureTime.day,
-                                   SimBriefSetupPage.monthNum2Name[departureTime.month-1],
-                                   departureTime.year%100)
-        plan["deph"] = str(departureTime.hour)
-        plan["depm"] = str(departureTime.minute)
-
-        arrivalTime = bookedFlight.arrivalTime
-        plan["steh"] = str(arrivalTime.hour)
-        plan["stem"] = str(arrivalTime.minute)
-
-        plan["manualzfw"] = str(wizard.zfw)
-        plan["cargo"] = str(wizard.bagWeight + wizard.cargoWeight + wizard.mailWeight)
-
-        plan["route"] = wizard.route
-        plan["fl"] = str(wizard.filedCruiseAltitude)
-        plan["altn"] = wizard.alternate
-
-        plan["addedfuel"] = "2.5" # FIXME: query
-        plan["origrwy"] = "" # FIXME: query
-        plan["destrwy"] = "" # FIXME: query
-        plan["climb"] = "250/300/78" # FIXME: query
-        plan["descent"] = "80/280/250" # FIXME: query
-        plan["cruise"] = "LRC" # FIXME: query
-
-        return plan
-
-#-----------------------------------------------------------------------------
-
-class SimBriefingPage(Page):
-    """Page to display the SimBrief HTML briefing."""
-    def __init__(self, wizard):
-        """Construct the setup page."""
-
-        super(SimBriefingPage, self).__init__(wizard,
-                                              "SimBrief flight plan", "")
-
-        self._alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                       xscale = 1.0, yscale = 1.0)
-
-        self.setMainWidget(self._alignment)
-
-        self.addCancelFlightButton()
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-        self._button.set_label(xstr("briefing_button"))
-        self._button.set_has_tooltip(False)
-        self._button.set_use_stock(False)
-
-    def activate(self):
-        """Activate the SimBrief flight plan page"""
-        container = cef.getContainer()
-        self._alignment.add(container)
-
-        self._browser = \
-          cef.startInContainer(container,
-                               "file://" + SimBriefSetupPage.getHTMLFilePath())
-
-    def _backClicked(self, button):
-        """Called when the Back button has been pressed."""
-        self.goBack()
-
-    def _forwardClicked(self, button):
-        """Called when the Forward button has been pressed."""
-        if not self._completed:
-            self._button.set_label(xstr("button_next"))
-            self._button.set_tooltip_text(xstr("button_next_tooltip"))
-            self._wizard.usingSimBrief = True
-            self.complete()
-
-        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_pre") +
-                                       xstr("fuel_help_post"),
-                                       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)
-
-        tankData = [(tank, 2500, 3900) for tank in acft.mostFuelTanks]
-        self._setupTanks(tankData)
-
-        self.addCancelFlightButton()
-
-        self._backButton = self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-        self._pumpIndex = 0
-
-    def activate(self):
-        """Activate the page."""
-        self._setupTanks(self._wizard._fuelData)
-
-        aircraft = self._wizard.gui.flight.aircraft
-        minLandingFuel = aircraft.minLandingFuel
-        recommendedLandingFuel = aircraft.recommendedLandingFuel
-
-        middleHelp = "" if minLandingFuel is None else \
-            (xstr("fuel_help_min") % (minLandingFuel,)) \
-            if recommendedLandingFuel is None else \
-            (xstr("fuel_help_min_rec") % (minLandingFuel,
-                                          recommendedLandingFuel))
-        self.setHelp(xstr("fuel_help_pre") + middleHelp + xstr("fuel_help_post"))
-
-    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()
-        elif self._wizard.usingSimBrief:
-            self._wizard.jumpPage(3)
-        else:
-            self._wizard.nextPage()
-
-    def _setupTanks(self, tankData):
-        """Setup the tanks for the given data."""
-        numTanks = len(tankData)
-        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)
-        index = 0
-        for (tank, current, capacity) in tankData:
-            fuelTank = FuelTank(tank,
-                                xstr("fuel_tank_" +
-                                     const.fuelTank2string(tank)),
-                                capacity, current)
-            self._fuelTable.attach(fuelTank, index, index+1, 0, 1)
-            self._fuelTanks.append(fuelTank)
-            index += 1
-
-        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()
-            if self._wizard.usingSimBrief:
-                self._wizard.gui.startMonitoring()
-                self._wizard.jumpPage(3)
-            else:
-                bookedFlight = self._wizard._bookedFlight
-                self._wizard.gui.beginBusy(xstr("route_down_notams"))
-                self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
-                                                      bookedFlight.departureICAO,
-                                                      bookedFlight.arrivalICAO)
-                startSound(const.SOUND_NOTAM)
-        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)
-
-    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._updatingMETAR = 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.addCancelFlightButton()
-
-        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 changeMETAR(self, metar):
-        """Change the METAR as a result of an edit on one of the other
-        pages."""
-        self._updatingMETAR = True
-        self._metar.get_buffer().set_text(metar)
-        self._updatingMETAR = False
-
-        self._updateButton()
-        self.metarEdited = True
-
-    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)
-                s += "-------------------- * --------------------\n"
-            buffer.set_text(s)
-
-        self._metarFrame.set_label(xstr("briefing_metar_template") % (icao,))
-        buffer = self._metar.get_buffer()
-        self._updatingMETAR = True
-        if metar is None:
-            buffer.set_text("")
-            self.setHelp(xstr("briefing_help_nometar"))
-        else:
-            buffer.set_text(metar)
-        self._updatingMETAR = False
-        self._updateButton()
-
-        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."""
-        print "BriefingPage.metarChanged", self._updatingMETAR
-        if not self._updatingMETAR:
-            self.metarEdited = True
-            self._updateButton()
-            metar = buffer.get_text(buffer.get_start_iter(),
-                                buffer.get_end_iter(), True)
-            self._wizard.metarChanged(metar, self)
-
-    def _metarInserted(self, textBuffer, iter, text, length):
-        """Called when new characters are inserted into the METAR.
-
-        It uppercases all characters."""
-        print "BriefingPage.metarInserted", self._updatingMETAR
-        if not self._updatingMETAR:
-            self._updatingMETAR = True
-
-            iter1 = iter.copy()
-            iter1.backward_chars(length)
-            textBuffer.delete(iter, iter1)
-
-            textBuffer.insert(iter, text.upper())
-
-            self._updatingMETAR = False
-
-    def _updateButton(self):
-        """Update the sensitivity of the Next button based on the contents of
-        the METAR field."""
-        buffer = self._metar.get_buffer()
-        self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
-                                                   buffer.get_end_iter(),
-                                                   True)!="")
-
-
-#-----------------------------------------------------------------------------
-
-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(9, 24)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        row = 0
-
-        label = gtk.Label(xstr("takeoff_metar"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, row, row+1)
-
-        self._metar = gtk.Entry()
-        self._metar.set_width_chars(40)
-        self._metar.set_tooltip_text(xstr("takeoff_metar_tooltip"))
-        self._metar.connect("changed", self._metarChanged)
-        self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
-        table.attach(self._metar, 1, 24, row, row+1)
-        label.set_mnemonic_widget(self._metar)
-
-        self._updatingMETAR = False
-
-        row += 1
-
-        label = gtk.Label(xstr("takeoff_runway"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, row, row+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, row, row+1)
-        label.set_mnemonic_widget(self._runway)
-
-        row += 1
-
-        label = gtk.Label(xstr("takeoff_sid"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, row, row+1)
-
-        if pygobject:
-            self._sid = gtk.ComboBox.new_with_model_and_entry(comboModel)
-        else:
-            self._sid = gtk.ComboBoxEntry(comboModel)
-
-        self._sid.set_entry_text_column(0)
-        self._sid.get_child().set_width_chars(10)
-        self._sid.set_tooltip_text(xstr("takeoff_sid_tooltip"))
-        self._sid.connect("changed", self._upperChangedComboBox)
-        table.attach(self._sid, 1, 3, row, row+1)
-        label.set_mnemonic_widget(self._sid)
-
-        row += 1
-
-        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, row, row+1)
-
-        self._v1 = IntegerEntry()
-        self._v1.set_width_chars(4)
-        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip_knots"))
-        self._v1.connect("integer-changed", self._valueChanged)
-        table.attach(self._v1, 2, 3, row, row+1)
-        label.set_mnemonic_widget(self._v1)
-
-        self._v1Unit = gtk.Label(xstr("label_knots"))
-        self._v1Unit.set_alignment(0.0, 0.5)
-        table.attach(self._v1Unit, 3, 4, row, row+1)
-
-        row += 1
-
-        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, row, row+1)
-
-        self._vr = IntegerEntry()
-        self._vr.set_width_chars(4)
-        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip_knots"))
-        self._vr.connect("integer-changed", self._valueChanged)
-        table.attach(self._vr, 2, 3, row, row+1)
-        label.set_mnemonic_widget(self._vr)
-
-        self._vrUnit = gtk.Label(xstr("label_knots"))
-        self._vrUnit.set_alignment(0.0, 0.5)
-        table.attach(self._vrUnit, 3, 4, row, row+1)
-
-        row += 1
-
-        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, row, row+1)
-
-        self._v2 = IntegerEntry()
-        self._v2.set_width_chars(4)
-        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip_knots"))
-        self._v2.connect("integer-changed", self._valueChanged)
-        table.attach(self._v2, 2, 3, row, row+1)
-        label.set_mnemonic_widget(self._v2)
-
-        self._v2Unit = gtk.Label(xstr("label_knots"))
-        self._v2Unit.set_alignment(0.0, 0.5)
-        table.attach(self._v2Unit, 3, 4, row, row+1)
-
-        row += 1
-
-        self._derateType = acft.DERATE_NONE
-
-        self._derateLabel = gtk.Label()
-        self._derateLabel.set_use_underline(True)
-        self._derateLabel.set_markup(xstr("takeoff_derate_tupolev"))
-        self._derateLabel.set_alignment(0.0, 0.5)
-        table.attach(self._derateLabel, 0, 1, row, row+1)
-
-        self._derate = gtk.Alignment()
-        table.attach(self._derate, 2, 4, row, row+1)
-        self._derateWidget = None
-        self._derateEntry = None
-        self._derateUnit = None
-        self._derateButtons = None
-
-        row += 1
-
-        self._antiIceOn = gtk.CheckButton(xstr("takeoff_antiice"))
-        self._antiIceOn.set_use_underline(True)
-        self._antiIceOn.set_tooltip_text(xstr("takeoff_antiice_tooltip"))
-        table.attach(self._antiIceOn, 2, 4, row, row+1)
-
-        row += 1
-
-        self._rto = gtk.CheckButton(xstr("takeoff_rto"))
-        self._rto.set_use_underline(True)
-        self._rto.set_tooltip_text(xstr("takeoff_rto_tooltip"))
-        self._rto.connect("toggled", self._rtoToggled)
-        table.attach(self._rto, 2, 4, row, row+1, ypadding = 8)
-
-        self.addCancelFlightButton()
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-        self._active = False
-
-    @property
-    def runway(self):
-        """Get the runway."""
-        return self._runway.get_text()
-
-    @property
-    def sid(self):
-        """Get the SID."""
-        text = self._sid.get_child().get_text()
-        return text if self._sid.get_active()!=0 and text and text!="N/A" \
-               else None
-
-    @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()
-
-    @property
-    def derate(self):
-        """Get the derate value, if any."""
-        if self._derateWidget is None:
-            return None
-        if self._derateType==acft.DERATE_BOEING:
-            derate = self._derateEntry.get_text()
-            return derate if derate else None
-        elif self._derateType==acft.DERATE_EPR:
-            derate = self._derateWidget.get_text()
-            return derate if derate else None
-        elif self._derateType==acft.DERATE_TUPOLEV:
-            return acft.DERATE_TUPOLEV_NOMINAL \
-                   if self._derateButtons[0].get_active() \
-                   else acft.DERATE_TUPOLEV_TAKEOFF
-        elif self._derateType==acft.DERATE_B462:
-            return self._derateWidget.get_active()
-        else:
-            return None
-
-    @property
-    def antiIceOn(self):
-        """Get whether the anti-ice system has been turned on."""
-        return self._antiIceOn.get_active()
-
-    @antiIceOn.setter
-    def antiIceOn(self, value):
-        """Set the anti-ice indicator."""
-        self._antiIceOn.set_active(value)
-
-    @property
-    def rtoIndicated(self):
-        """Get whether the pilot has indicated if there was an RTO."""
-        return self._rto.get_active()
-
-    def activate(self):
-        """Activate the page."""
-        print "TakeoffPage.activate"
-
-        self._updatingMETAR = True
-        self._metar.get_buffer().set_text(self._wizard.departureMETAR, -1)
-        self._updatingMETAR = False
-
-        self._runway.set_text("")
-        self._runway.set_sensitive(True)
-        self._sid.set_active(0)
-        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)
-
-        i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
-        speedUnit = xstr("label" + i18nSpeedUnit)
-        self._v1Unit.set_text(speedUnit)
-        self._vrUnit.set_text(speedUnit)
-        self._v2Unit.set_text(speedUnit)
-
-        self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip" + i18nSpeedUnit))
-        self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip" + i18nSpeedUnit))
-        self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip" + i18nSpeedUnit))
-
-        self._derateType = self._wizard.gui.flight.aircraft.derateType
-
-        self._setupDerateWidget()
-
-        self._rto.set_active(False)
-        self._rto.set_sensitive(False)
-
-        self._button.set_sensitive(False)
-        self._forwardAllowed = False
-
-        self._active = True
-
-    def allowForward(self):
-        """Allow going to the next page."""
-        print "TakeoffPage.allowForward"
-        self._forwardAllowed = True
-        self._updateForwardButton()
-
-    def reset(self):
-        """Reset the page if the wizard is reset."""
-        print "TakeoffPage.reset"
-
-        super(TakeoffPage, self).reset()
-        self._v1.reset()
-        self._vr.reset()
-        self._v2.reset()
-        self._hasDerate = False
-        self._antiIceOn.set_active(False)
-        self._active = False
-
-    def setRTOEnabled(self, enabled):
-        """Set the RTO checkbox enabled or disabled."""
-        if not enabled:
-            self._rto.set_active(False)
-        self._rto.set_sensitive(enabled)
-
-    def changeMETAR(self, metar):
-        """Change the METAR as a result of an edit on one of the other
-        pages."""
-        if self._active:
-            print "TakeoffPage.changeMETAR"
-            self._updatingMETAR = True
-            self._metar.get_buffer().set_text(metar, -1)
-            self._updatingMETAR = False
-
-            self._updateForwardButton()
-
-    def _updateForwardButton(self):
-        """Update the sensitivity of the forward button based on some conditions."""
-        sensitive = self._forwardAllowed and \
-                    self._metar.get_text()!="" and \
-                    self._runway.get_text()!="" and \
-                    self.sid is not None 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 and \
-                    (self._derateType==acft.DERATE_NONE or
-                     self.derate is not None)
-
-        print "TakeoffPage._updateForwardButton: forwardAllowed:", self._forwardAllowed, ", sensitive:", sensitive
-        if self._forwardAllowed:
-            print "  METAR: ", self._metar.get_text()
-            print "  runway: ", self._runway.get_text()
-            print "  SID:", self.sid
-            print "  V1:", self.v1
-            print "  VR:", self.vr
-            print "  V2:", self.v2
-            print "  derateType:", self._derateType
-            print "  derate:", self.derate
-
-        self._button.set_sensitive(sensitive)
-
-    def _valueChanged(self, widget, arg = None):
-        """Called when the value of some widget has changed."""
-        print "TakeoffPage._valueChanged"
-
-        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."""
-        print "TakeoffPage._upperChanged"
-        entry.set_text(entry.get_text().upper())
-        self._valueChanged(entry, arg)
-
-    def _upperChangedComboBox(self, comboBox):
-        """Called for combo box widgets that must be converted to uppercase."""
-        entry = comboBox.get_child()
-        if comboBox.get_active()==-1:
-            entry.set_text(entry.get_text().upper())
-        self._valueChanged(entry)
-
-    def _derateChanged(self, entry):
-        """Called when the value of the derate is changed."""
-        print "TakeoffPage._derateChanged"
-        self._updateForwardButton()
-
-    def _rtoToggled(self, button):
-        """Called when the RTO check button is toggled."""
-        self._wizard.rtoToggled(button.get_active())
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _forwardClicked(self, button):
-        """Called when the forward button is clicked."""
-        aircraft = self._wizard.gui.flight.aircraft
-        aircraft.updateV1R2()
-        if self.derate is not None:
-            aircraft.updateDerate()
-        aircraft.updateTakeoffAntiIce()
-        self._wizard.nextPage()
-
-    def _setupDerateWidget(self):
-        """Setup the derate widget."""
-        if self._derateWidget is not None:
-            self._derate.remove(self._derateWidget)
-
-        if self._derateType==acft.DERATE_BOEING:
-            self._derateLabel.set_text(xstr("takeoff_derate_boeing"))
-            self._derateLabel.set_use_underline(True)
-            self._derateLabel.set_sensitive(True)
-
-            self._derateEntry = gtk.Entry()
-            self._derateEntry.set_width_chars(7)
-            self._derateEntry.set_tooltip_text(xstr("takeoff_derate_boeing_tooltip"))
-            self._derateEntry.set_alignment(1.0)
-            self._derateEntry.connect("changed", self._derateChanged)
-            self._derateLabel.set_mnemonic_widget(self._derateEntry)
-
-            self._derateUnit = gtk.Label("%")
-            self._derateUnit.set_alignment(0.0, 0.5)
-
-            self._derateWidget = gtk.Table(3, 1)
-            self._derateWidget.set_row_spacings(4)
-            self._derateWidget.set_col_spacings(16)
-            self._derateWidget.set_homogeneous(False)
-
-            self._derateWidget.attach(self._derateEntry, 0, 2, 0, 1)
-            self._derateWidget.attach(self._derateUnit, 2, 3, 0, 1)
-
-            self._derate.add(self._derateWidget)
-        elif self._derateType==acft.DERATE_EPR:
-            self._derateLabel.set_text("_EPR:")
-            self._derateLabel.set_use_underline(True)
-            self._derateLabel.set_sensitive(True)
-
-            self._derateWidget = gtk.Entry()
-            self._derateWidget.set_width_chars(7)
-            self._derateWidget.set_tooltip_text(xstr("takeoff_derate_epr_tooltip"))
-            self._derateWidget.set_alignment(1.0)
-            self._derateWidget.connect("changed", self._derateChanged)
-            self._derateLabel.set_mnemonic_widget(self._derateWidget)
-
-            self._derate.add(self._derateWidget)
-        elif self._derateType==acft.DERATE_TUPOLEV:
-            self._derateLabel.set_text(xstr("takeoff_derate_tupolev"))
-            self._derateLabel.set_use_underline(True)
-            self._derateLabel.set_sensitive(True)
-
-            if pygobject:
-                nominal = gtk.RadioButton.\
-                  new_with_label_from_widget(None,
-                                             xstr("takeoff_derate_tupolev_nominal"))
-            else:
-                nominal = gtk.RadioButton(None,
-                                          xstr("takeoff_derate_tupolev_nominal"))
-            nominal.set_use_underline(True)
-            nominal.set_tooltip_text(xstr("takeoff_derate_tupolev_nominal_tooltip"))
-            nominal.connect("toggled", self._derateChanged)
-
-            if pygobject:
-                takeoff = gtk.RadioButton.\
-                  new_with_label_from_widget(nominal,
-                                             xstr("takeoff_derate_tupolev_takeoff"))
-            else:
-                takeoff = gtk.RadioButton(nominal,
-                                          xstr("takeoff_derate_tupolev_takeoff"))
-
-            takeoff.set_use_underline(True)
-            takeoff.set_tooltip_text(xstr("takeoff_derate_tupolev_takeoff_tooltip"))
-            takeoff.connect("toggled", self._derateChanged)
-
-            self._derateButtons = [nominal, takeoff]
-
-            self._derateWidget = gtk.HBox()
-            self._derateWidget.pack_start(nominal, False, False, 4)
-            self._derateWidget.pack_start(takeoff, False, False, 4)
-
-            self._derate.add(self._derateWidget)
-        elif self._derateType==acft.DERATE_B462:
-            self._derateLabel.set_text("")
-
-            self._derateWidget = gtk.CheckButton(xstr("takeoff_derate_b462"))
-            self._derateWidget.set_tooltip_text(xstr("takeoff_derate_b462_tooltip"))
-            self._derateWidget.set_use_underline(True)
-            self._derate.add(self._derateWidget)
-        else:
-            self._derateWidget = None
-            self._derateLabel.set_text("")
-            self._derateLabel.set_sensitive(False)
-
-    def _metarChanged(self, entry):
-        """Called when the METAR has changed."""
-        print "TakeoffPage.metarChanged", self._updatingMETAR
-        if not self._updatingMETAR:
-            self._updateForwardButton()
-            self._wizard.metarChanged(entry.get_text(), self)
-
-    def _metarInserted(self, buffer, position, text, length):
-        """Called when new characters are inserted into the METAR.
-
-        It uppercases all characters."""
-        print "TakeoffPage.metarInserted", self._updatingMETAR
-        if not self._updatingMETAR:
-            self._updatingMETAR = True
-
-            buffer.delete_text(position, length)
-            buffer.insert_text(position, text.upper(), length)
-
-            self._updatingMETAR = False
-
-#-----------------------------------------------------------------------------
-
-class CruisePage(Page):
-    """The page containing the flight level that might change during flight."""
-    def __init__(self, wizard):
-        """Construct the page."""
-        super(CruisePage, self).__init__(wizard, xstr("cruise_title"),
-                                         xstr("cruise_help"))
-
-        self._loggable = False
-        self._loggedCruiseLevel = 240
-        self._activated = False
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
-                                  xscale = 0.0, yscale = 1.0)
-
-        mainBox = gtk.VBox()
-        alignment.add(mainBox)
-        self.setMainWidget(alignment)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 0.0, yscale = 0.0)
-        mainBox.pack_start(alignment, False, False, 16)
-
-        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("cruise_route_level_tooltip"))
-        self._cruiseLevel.set_numeric(True)
-        self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
-        label.set_mnemonic_widget(self._cruiseLevel)
-
-        levelBox.pack_start(self._cruiseLevel, False, False, 8)
-
-        self._updateButton = gtk.Button(xstr("cruise_route_level_update"));
-        self._updateButton.set_use_underline(True)
-        self._updateButton.set_tooltip_text(xstr("cruise_route_level_update_tooltip"))
-        self._updateButton.connect("clicked", self._updateButtonClicked)
-
-        levelBox.pack_start(self._updateButton, False, False, 16)
-
-        mainBox.pack_start(levelBox, False, False, 0)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 0.0, yscale = 1.0)
-        mainBox.pack_start(alignment, True, True, 0)
-
-        self.addCancelFlightButton()
-
-        self._backButton = self.addPreviousButton(clicked = self._backClicked)
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-    @property
-    def activated(self):
-        """Determine if the page is already activated or not."""
-        return self._activated
-
-    @property
-    def cruiseLevel(self):
-        """Get the cruise level."""
-        return self._loggedCruiseLevel
-
-    @property
-    def loggableCruiseLevel(self):
-        """Get the cruise level which should be logged."""
-        return self._cruiseLevel.get_value_as_int()
-
-    def setLoggable(self, loggable):
-        """Set whether the cruise altitude can be logged."""
-        self._loggable = loggable
-        self._updateButtons()
-
-    def activate(self):
-        """Setup the route from the booked flight."""
-        self._loggedCruiseLevel = self._wizard.filedCruiseLevel
-        self._cruiseLevel.set_value(self._loggedCruiseLevel)
-        self._activated = True
-
-    def reset(self):
-        """Reset the page."""
-        self._loggable = False
-        self._activated = False
-        super(CruisePage, self).reset()
-
-    def _updateButtons(self):
-        """Update the sensitivity of the buttons."""
-        self._updateButton.set_sensitive(self._loggable and
-                                         self.loggableCruiseLevel!=
-                                         self._loggedCruiseLevel)
-
-    def _cruiseLevelChanged(self, spinButton):
-        """Called when the cruise level has changed."""
-        self._updateButtons()
-
-    def _updateButtonClicked(self, button):
-        """Called when the update button is clicked."""
-        if self._wizard.cruiseLevelChanged():
-            self._loggedCruiseLevel = self.loggableCruiseLevel
-            self._updateButtons()
-
-    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(7, 24)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        row = 0
-
-        label = gtk.Label(xstr("landing_metar"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 0, 1, row, row+1)
-
-        self._metar = gtk.Entry()
-        self._metar.set_width_chars(40)
-        self._metar.set_tooltip_text(xstr("landing_metar_tooltip"))
-        self._metar.connect("changed", self._metarChanged)
-        self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
-        table.attach(self._metar, 1, 24, row, row+1)
-        label.set_mnemonic_widget(self._metar)
-
-        self._updatingMETAR = False
-
-        row += 1
-
-        label = gtk.Label(xstr("landing_star"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, row, row + 1)
-
-        if pygobject:
-            self._star = gtk.ComboBox.new_with_model_and_entry(comboModel)
-        else:
-            self._star = gtk.ComboBoxEntry(comboModel)
-
-        self._star.set_entry_text_column(0)
-        self._star.get_child().set_width_chars(10)
-        self._star.set_tooltip_text(xstr("landing_star_tooltip"))
-        self._star.connect("changed", self._upperChangedComboBox)
-        self._star.set_sensitive(False)
-        table.attach(self._star, 2, 4, row, row + 1)
-        label.set_mnemonic_widget(self._star)
-
-        row += 1
-
-        label = gtk.Label(xstr("landing_transition"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, row, row + 1)
-
-        if pygobject:
-            self._transition = gtk.ComboBox.new_with_model_and_entry(comboModel)
-        else:
-            self._transition = gtk.ComboBoxEntry(comboModel)
-
-        self._transition.set_entry_text_column(0)
-        self._transition.get_child().set_width_chars(10)
-        self._transition.set_tooltip_text(xstr("landing_transition_tooltip"))
-        self._transition.connect("changed", self._upperChangedComboBox)
-        self._transition.set_sensitive(False)
-        table.attach(self._transition, 2, 4, row, row + 1)
-        label.set_mnemonic_widget(self._transition)
-
-        row += 1
-
-        label = gtk.Label(xstr("landing_runway"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, row, row + 1)
-
-        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, row, row + 1)
-        label.set_mnemonic_widget(self._runway)
-
-        row += 1
-
-        label = gtk.Label(xstr("landing_approach"))
-        label.set_use_underline(True)
-        label.set_alignment(0.0, 0.5)
-        table.attach(label, 1, 2, row, row + 1)
-
-        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, row, row + 1)
-        label.set_mnemonic_widget(self._approachType)
-
-        row += 1
-
-        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, row, row + 1)
-
-        self._vref = IntegerEntry()
-        self._vref.set_width_chars(5)
-        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip_knots"))
-        self._vref.connect("integer-changed", self._vrefChanged)
-        table.attach(self._vref, 3, 4, row, row + 1)
-        label.set_mnemonic_widget(self._vref)
-
-        self._vrefUnit = gtk.Label(xstr("label_knots"))
-        table.attach(self._vrefUnit, 4, 5, row, row + 1)
-
-        row += 1
-
-        self._antiIceOn = gtk.CheckButton(xstr("landing_antiice"))
-        self._antiIceOn.set_use_underline(True)
-        self._antiIceOn.set_tooltip_text(xstr("landing_antiice_tooltip"))
-        table.attach(self._antiIceOn, 3, 5, row, row + 1)
-
-        self.addCancelFlightButton()
-
-        self.addPreviousButton(clicked = self._backClicked)
-
-        self._button = self.addNextButton(clicked = self._forwardClicked)
-
-        self._active = False
-
-    @property
-    def star(self):
-        """Get the STAR or None if none entered."""
-        text = self._star.get_child().get_text()
-        return text if self._star.get_active()!=0 and text and text!="N/A" \
-               else None
-
-    @property
-    def transition(self):
-        """Get the transition or None if none entered."""
-        text = self._transition.get_child().get_text()
-        return text if self._transition.get_active()!=0 and text and text!="N/A" \
-               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()
-
-    @property
-    def antiIceOn(self):
-        """Get whether the anti-ice system has been turned on."""
-        return self._antiIceOn.get_active()
-
-    @antiIceOn.setter
-    def antiIceOn(self, value):
-        """Set the anti-ice indicator."""
-        self._antiIceOn.set_active(value)
-
-    def reset(self):
-        """Reset the page if the wizard is reset."""
-        super(LandingPage, self).reset()
-        self._vref.reset()
-        self._antiIceOn.set_active(False)
-        self._flightEnded = False
-        self._active = False
-
-    def activate(self):
-        """Called when the page is activated."""
-        self._updatingMETAR = True
-        self._metar.get_buffer().set_text(self._wizard.arrivalMETAR, -1)
-        self._updatingMETAR = False
-
-        self._star.set_active(0)
-        self._star.set_sensitive(True)
-
-        self._transition.set_active(0)
-        self._transition.set_sensitive(True)
-
-        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)
-
-        i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
-        speedUnit = xstr("label" + i18nSpeedUnit)
-        self._vrefUnit.set_text(speedUnit)
-
-        self._vref.set_tooltip_markup(xstr("landing_vref_tooltip" +
-                                           i18nSpeedUnit))
-
-        self._updateForwardButton()
-
-        self._active = True
-
-    def flightEnded(self):
-        """Called when the flight has ended."""
-        super(LandingPage, self).flightEnded()
-        self._flightEnded = True
-        self._updateForwardButton()
-
-    def changeMETAR(self, metar):
-        """Change the METAR as a result of an edit on one of the other
-        pages."""
-        if self._active:
-            print "LandingPage.changeMETAR"
-            self._updatingMETAR = True
-            self._metar.get_buffer().set_text(metar, -1)
-            self._updatingMETAR = False
-
-            self._updateForwardButton()
-
-    def _updateForwardButton(self):
-        """Update the sensitivity of the forward button."""
-        sensitive = self._flightEnded and \
-                    self._metar.get_text()!="" and \
-                    (self.star is not None or
-                     self.transition is not None) 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 _upperChangedComboBox(self, comboBox):
-        """Called for combo box widgets that must be converted to uppercase."""
-        if comboBox.get_active()==-1:
-            entry = comboBox.get_child()
-            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."""
-        aircraft = self._wizard.gui.flight.aircraft
-        aircraft.updateVRef()
-        aircraft.updateLandingAntiIce()
-        if self._wizard.gui.config.onlineGateSystem and \
-           self._wizard.loggedIn and not self._completed and \
-           self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
-           not self._wizard.entranceExam:
-            self._wizard.getFleet(callback = self._fleetRetrieved,
-                                  force = True)
-        else:
-            self._wizard.nextPage()
-
-    def _fleetRetrieved(self, fleet):
-        """Callback for the fleet retrieval."""
-        self._wizard.nextPage()
-
-    def _metarChanged(self, entry):
-        """Called when the METAR has changed."""
-        print "LandingPage.metarChanged", self._updatingMETAR
-        if not self._updatingMETAR:
-            self._updateForwardButton()
-            self._wizard.metarChanged(entry.get_text(), self)
-
-    def _metarInserted(self, buffer, position, text, length):
-        """Called when new characters are inserted into the METAR.
-
-        It uppercases all characters."""
-        print "LandingPage.metarInserted", self._updatingMETAR
-        if not self._updatingMETAR:
-            self._updatingMETAR = True
-
-            buffer.delete_text(position, length)
-            buffer.insert_text(position, text.upper(), length)
-
-            self._updatingMETAR = False
-
-#-----------------------------------------------------------------------------
-
-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."""
-        help = xstr("finish_help") + xstr("finish_help_goodtime")
-        super(FinishPage, self).__init__(wizard, xstr("finish_title"), help)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                  xscale = 0.0, yscale = 0.0)
-
-        table = gtk.Table(10, 2)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(False)
-        alignment.add(table)
-        self.setMainWidget(alignment)
-
-        row = 0
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_rating"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+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, row, row+1)
-
-        row += 1
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_dep_time"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+1)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._depTime = gtk.Label()
-        self._depTime.set_width_chars(13)
-        self._depTime.set_alignment(0.0, 0.5)
-        self._depTime.set_use_markup(True)
-        self._depTime.set_tooltip_markup(xstr("finish_dep_time_tooltip"))
-        labelAlignment.add(self._depTime)
-        table.attach(labelAlignment, 1, 2, row, row+1)
-
-        row += 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, row, row+1)
-
-        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, row, row+1)
-
-        row += 1
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_block_time"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+1)
-
-        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, row, row+1)
-
-        row += 1
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_arr_time"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+1)
-
-        labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
-        self._arrTime = gtk.Label()
-        self._arrTime.set_width_chars(13)
-        self._arrTime.set_alignment(0.0, 0.5)
-        self._arrTime.set_use_markup(True)
-        self._arrTime.set_tooltip_markup(xstr("finish_arr_time_tooltip"))
-        labelAlignment.add(self._arrTime)
-        table.attach(labelAlignment, 1, 2, row, row+1)
-
-        row += 1
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_distance"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+1)
-
-        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, row, row+1)
-
-        row += 1
-
-        labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
-        label = gtk.Label(xstr("finish_fuel"))
-        labelAlignment.add(label)
-        table.attach(labelAlignment, 0, 1, row, row+1)
-
-        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, row, row+1)
-
-        row += 1
-
-        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, row, row+1)
-
-        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, row, row+1)
-        label.set_mnemonic_widget(self._flightType)
-
-        row += 1
-
-        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, row, row + 1)
-
-        row += 1
-
-        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, row, row+1)
-
-        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, row, row+1)
-        self._gateLabel.set_mnemonic_widget(self._gate)
-
-        self.addButton(xstr("finish_newFlight"),
-                       sensitive = True,
-                       clicked = self._newFlightClicked,
-                       tooltip = xstr("finish_newFlight_tooltip"),
-                       padding = 16)
-
-        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._tooBigTimeDifference = False
-        self._deferredAutoSave = False
-        self._pirepSaved = False
-        self._pirepSent = False
-
-        self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
-                                          sensitive = False,
-                                          clicked = self._sendClicked,
-                                          tooltip = xstr("sendPIREP_tooltip"))
-
-        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 10*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 20*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 0*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), (23*60.0+50)*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (1*60.0+5)*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (0*60.0+50)*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (0*60.0+5)*60.0)
-        # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (23*60.0+45)*60.0)
-
-    @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."""
-        self._deferredAutoSave = False
-        self._pirepSaved = False
-        self._pirepSent = False
-
-        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(self._wizard.loggedIn)
-
-        self._gatesModel.clear()
-        if self._wizard.gui.config.onlineGateSystem and \
-           self._wizard.loggedIn and \
-           self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
-           not self._wizard.entranceExam:
-            occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
-            for gate in lhbpGates.gates:
-                if gate.isAvailable(lhbpGates, occupiedGateNumbers):
-                    self._gatesModel.append([gate.number])
-            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)
-
-        self._updateTimes()
-
-    def updateButtons(self):
-        """Update the sensitivity state of the buttons."""
-        gui = self._wizard.gui
-        faultsExplained = gui.faultsFullyExplained
-        timesCorrect = self.flightType is None or \
-                       not self._tooBigTimeDifference or \
-                       gui.hasComments or gui.hasDelayCode
-        sensitive = gui.flight is not None and \
-                    gui.flight.stage==const.STAGE_END and \
-                    self._flightType.get_active()>=0 and \
-                    (self._gatesModel.get_iter_first() is None or
-                     self._gate.get_active()>=0) and \
-                     faultsExplained and timesCorrect
-
-        self._updateHelp(faultsExplained, timesCorrect)
-
-        wasSensitive = self._saveButton.get_sensitive()
-
-        if gui.config.pirepAutoSave and sensitive and not wasSensitive:
-            if gui.isWizardActive():
-                self._autoSavePIREP()
-            else:
-                self._deferredAutoSave = True
-
-        if not sensitive:
-            self._deferredAutoSave = False
-
-        self._saveButton.set_sensitive(sensitive)
-        self._sendButton.set_sensitive(sensitive and
-                                       self._wizard.bookedFlight.id is not None)
-
-    def grabDefault(self):
-        """If the page has a default button, make it the default one."""
-        super(FinishPage, self).grabDefault()
-        if self._deferredAutoSave:
-            self._autoSavePIREP()
-            self._deferredAutoSave = False
-
-    def _autoSavePIREP(self):
-        """Perform the automatic saving of the PIREP."""
-        self._lastSavePath = os.path.join(self._wizard.gui.config.pirepDirectory,
-                                          self._getDefaultPIREPName())
-        self._lastSavePath = text2unicode(self._lastSavePath)
-        self._savePIREP(automatic = True)
-
-    def _backClicked(self, button):
-        """Called when the Back button is pressed."""
-        self.goBack()
-
-    def _flightTypeChanged(self, comboBox):
-        """Called when the flight type has changed."""
-        self._updateTimes()
-
-    def _gateChanged(self, comboBox):
-        """Called when the arrival gate has changed."""
-        self.updateButtons()
-
-    def _newFlightClicked(self, button):
-        """Called when the new flight button is clicked."""
-        gui = self._wizard.gui
-        if not self._pirepSent and not self._pirepSaved:
-            dialog = gtk.MessageDialog(parent = gui.mainWindow,
-                                       type = MESSAGETYPE_QUESTION,
-                                       message_format = xstr("finish_newFlight_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:
-                return
-
-        gui.reset()
-
-    def _getDefaultPIREPName(self):
-        """Get the default name of the PIREP."""
-        gui = self._wizard.gui
-
-        bookedFlight = gui.bookedFlight
-        tm = time.gmtime()
-
-        pilotID = self._wizard.pilotID
-        if pilotID: pilotID += " "
-        return "%s%s %02d%02d %s-%s.pirep" % \
-               (pilotID, str(bookedFlight.departureTime.date()),
-                tm.tm_hour, tm.tm_min,
-                bookedFlight.departureICAO, bookedFlight.arrivalICAO)
-
-
-    def _saveClicked(self, button):
-        """Called when the Save PIREP button is clicked."""
-        gui = self._wizard.gui
-
-        fileName = self._getDefaultPIREPName()
-
-        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:
-            self._lastSavePath = text2unicode(dialog.get_filename())
-            self._savePIREP()
-
-    def _savePIREP(self, automatic = False):
-        """Perform the saving of the PIREP."""
-
-        gui = self._wizard.gui
-
-        if automatic:
-            gui.beginBusy(xstr("finish_autosave_busy"))
-
-        pirep = PIREP(gui.flight)
-        error = pirep.save(self._lastSavePath)
-
-        if automatic:
-            gui.endBusy()
-
-        if error:
-            type = MESSAGETYPE_ERROR
-            message = xstr("finish_save_failed")
-            secondary = xstr("finish_save_failed_sec") % (text2unicode(error),)
-        else:
-            type = MESSAGETYPE_INFO
-            message = xstr("finish_save_done")
-            if automatic:
-                secondary = xstr("finish_save_done_sec") % (self._lastSavePath,)
-            else:
-                secondary = None
-            self._pirepSaved = True
-
-        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("file_filter_pireps"))
-            filter.add_pattern("*.pirep")
-            dialog.add_filter(filter)
-
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("file_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.flight)
-        self._wizard.gui.sendPIREP(pirep,
-                                   callback = self._handlePIREPSent)
-
-    def _handlePIREPSent(self, returned, result):
-        """Callback for the PIREP sending result."""
-        self._pirepSent = returned and result.success
-        if self._wizard.gui.config.onlineGateSystem and \
-           self._wizard.loggedIn and not self._wizard.entranceExam 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
-
-    def _formatTime(self, scheduledTime, realTimestamp, (warning, error)):
-        """Format the departure or arrival time based on the given data as a
-        markup for a label."""
-        realTime = time.gmtime(realTimestamp)
-
-        if warning:
-            colour = "red" if error else "orange"
-            markupBegin = '<span foreground="%s">' % (colour,)
-            markupEnd = '</span>'
-        else:
-            markupBegin = markupEnd = ""
-
-        markup = "<b>%s%02d:%02d [%02d:%02d]%s</b>" % \
-                 (markupBegin,
-                  realTime.tm_hour, realTime.tm_min,
-                  scheduledTime.hour, scheduledTime.minute,
-                  markupEnd)
-
-        return markup
-
-    def _updateTimes(self):
-        """Format the flight times and the help text according to the flight
-        type.
-
-        The buttons are also updated.
-        """
-        flight = self._wizard.gui._flight
-        bookedFlight = flight.bookedFlight
-
-        (departureWarning, departureError) = flight.blockTimeStartWrong
-        (arrivalWarning, arrivalError) = flight.blockTimeEndWrong
-
-        if self.flightType==const.FLIGHTTYPE_VIP:
-            departureError = arrivalError = False
-
-        self._tooBigTimeDifference = departureError or arrivalError
-
-        self._depTime.set_markup(self._formatTime(bookedFlight.departureTime,
-                                                  flight.blockTimeStart,
-                                                  (departureWarning,
-                                                   departureError)))
-
-        self._arrTime.set_markup(self._formatTime(bookedFlight.arrivalTime,
-                                                  flight.blockTimeEnd,
-                                                  (arrivalWarning,
-                                                   arrivalError)))
-
-        self.updateButtons()
-
-    def _updateHelp(self, faultsExplained, timesCorrect):
-        """Update the help text according to the actual situation."""
-        if not faultsExplained:
-            self.setHelp(xstr("finish_help") + xstr("finish_help_faults"))
-        elif not timesCorrect:
-            self.setHelp(xstr("finish_help") + xstr("finish_help_wrongtime"))
-        else:
-            self.setHelp(xstr("finish_help") + xstr("finish_help_goodtime"))
-
-
-#-----------------------------------------------------------------------------
-
-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._loginPage = LoginPage(self)
-        self._pages.append(self._loginPage)
-        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._routePage = RoutePage(self)
-        self._pages.append(self._routePage)
-        self._simBriefSetupPage = SimBriefSetupPage(self)
-        self._pages.append(self._simBriefSetupPage)
-        self._simBriefingPage = SimBriefingPage(self)
-        self._pages.append(self._simBriefingPage)
-        self._pages.append(FuelPage(self))
-        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._cruisePage = CruisePage(self)
-        self._pages.append(self._cruisePage)
-        self._landingPage = LandingPage(self)
-        self._pages.append(self._landingPage)
-        self._finishPage = FinishPage(self)
-        self._pages.append(self._finishPage)
-
-        self._requestedWidth = None
-        self._requestedHeight = None
-
-        self.connect("size-allocate", self._sizeAllocate)
-
-        for page in self._pages:
-            page.show_all()
-            page.setStyle()
-
-        self._initialize()
-
-    def _sizeAllocate(self, widget, allocation):
-        if self._requestedWidth is not None and \
-           self._requestedHeight is not None:
-           return
-
-        if self._currentPage is not None:
-            self.remove(self._pages[self._currentPage])
-
-        maxWidth = 0
-        maxHeight = 0
-        for page in self._pages:
-            self.add(page)
-            self.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)
-            self.remove(page)
-
-        if self._currentPage is not None:
-            self.add(self._pages[self._currentPage])
-
-        self._requestedWidth = maxWidth
-        self._requestedHeight = maxHeight
-        self.set_size_request(maxWidth, maxHeight)
-
-    @property
-    def pilotID(self):
-        """Get the pilot ID, if given."""
-        return self._loginPage.pilotID
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is about to be taken."""
-        return self._loginPage.entranceExam
-
-    @property
-    def loggedIn(self):
-        """Indicate if there was a successful login."""
-        return self._loginResult is not None
-
-    @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 numCrew(self):
-        """Get the number of crew members."""
-        return self._payloadPage.numCrew
-
-    @property
-    def numPassengers(self):
-        """Get the number of passengers."""
-        return self._payloadPage.numPassengers
-
-    @property
-    def bagWeight(self):
-        """Get the baggage weight."""
-        return self._payloadPage.bagWeight
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight."""
-        return self._payloadPage.cargoWeight
-
-    @property
-    def mailWeight(self):
-        """Get the mail weight."""
-        return self._payloadPage.mailWeight
-
-    @property
-    def zfw(self):
-        """Get the calculated ZFW value."""
-        return 0 if self._bookedFlight is None \
-               else self._payloadPage.calculateZFW()
-
-    @property
-    def filedCruiseLevel(self):
-        """Get the filed cruise level."""
-        return self._routePage.filedCruiseLevel
-
-    @property
-    def filedCruiseAltitude(self):
-        """Get the filed cruise altitude."""
-        return self._routePage.filedCruiseLevel * 100
-
-    @property
-    def cruiseAltitude(self):
-        """Get the cruise altitude."""
-        level = self._cruisePage.cruiseLevel if self._cruisePage.activated \
-                else self._routePage.filedCruiseLevel
-        return level * 100
-
-    @property
-    def loggableCruiseAltitude(self):
-        """Get the cruise altitude that can be logged."""
-        if self._cruisePage.activated:
-            return self._cruisePage.loggableCruiseLevel * 100
-        else:
-            return 0
-
-    @property
-    def route(self):
-        """Get the route."""
-        return self._routePage.route
-
-    @property
-    def alternate(self):
-        """Get the ICAO code of the alternate airport."""
-        return self._routePage.alternate
-
-    @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 derate(self):
-        """Get the derate value."""
-        return self._takeoffPage.derate
-
-    @property
-    def takeoffAntiIceOn(self):
-        """Get whether the anti-ice system was on during take-off."""
-        return self._takeoffPage.antiIceOn
-
-    @takeoffAntiIceOn.setter
-    def takeoffAntiIceOn(self, value):
-        """Set anti-ice on indicator."""
-        self._takeoffPage.antiIceOn = value
-
-    @property
-    def rtoIndicated(self):
-        """Get whether the pilot has indicated that an RTO has occured."""
-        return self._takeoffPage.rtoIndicated
-
-    @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 landingAntiIceOn(self):
-        """Get whether the anti-ice system was on during landing."""
-        return self._landingPage.antiIceOn
-
-    @landingAntiIceOn.setter
-    def landingAntiIceOn(self, value):
-        """Set anti-ice on indicator."""
-        self._landingPage.antiIceOn = value
-
-    @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
-
-    @property
-    def usingSimBrief(self):
-        """Indicate if we are using a SimBrief briefing or not."""
-        return self._usingSimBrief
-
-    @usingSimBrief.setter
-    def usingSimBrief(self, x):
-        """Set whether we are using a SimBrief briefing or not."""
-        self._usingSimBrief = x
-
-    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, loginResult):
-        """Resets the wizard to go back to the login page."""
-        self._initialize(keepLoginResult = loginResult is None,
-                         loginResult = loginResult)
-
-    def setStage(self, stage):
-        """Set the flight stage to the given one."""
-        if stage!=const.STAGE_END:
-            self._cruisePage.setLoggable(Flight.canLogCruiseAltitude(stage))
-
-        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:
-            for page in self._pages:
-                page.flightEnded()
-
-    def _initialize(self, keepLoginResult = False, loginResult = None):
-        """Initialize the wizard."""
-        if not keepLoginResult:
-            self._loginResult = loginResult
-
-        self._loginCallback = None
-
-        self._fleet = None
-        self._fleetCallback = None
-
-        self._bookedFlight = None
-        self._departureGate = "-"
-        self._fuelData = None
-        self._departureNOTAMs = None
-        self._departureMETAR = None
-        self._arrivalNOTAMs = None
-        self._arrivalMETAR = None
-        self._usingSimBrief = False
-
-        firstPage = 0 if self._loginResult is None else 1
-        for page in self._pages[firstPage:]:
-            page.reset()
-
-        self.setCurrentPage(firstPage)
-        #self.setCurrentPage(10)
-
-    def login(self, callback, pilotID, password, entranceExam):
-        """Called when the login button was clicked."""
-        self._loginCallback = callback
-        if pilotID is None:
-            loginResult = self._loginResult
-            assert loginResult is not None and loginResult.loggedIn
-            pilotID = loginResult.pilotID
-            password = loginResult.password
-            entranceExam = loginResult.entranceExam
-            busyMessage = xstr("reload_busy")
-        else:
-            self._loginResult = None
-            busyMessage = xstr("login_busy")
-
-        self.gui.beginBusy(busyMessage)
-
-        self.gui.webHandler.login(self._loginResultCallback,
-                                  pilotID, password,
-                                  entranceExam = entranceExam)
-
-    def reloadFlights(self, callback):
-        """Reload the flights from the MAVA server."""
-        self.login(callback, None, None, None)
-
-    def cruiseLevelChanged(self):
-        """Called when the cruise level is changed."""
-        return self.gui.cruiseLevelChanged()
-
-    def metarChanged(self, metar, originator):
-        """Called when a METER is changed on on of the pages.
-
-        originator is the page that originated the changed. It will be used to
-        determine which METAR (departure or arrival) has changed."""
-        metar = metar.upper()
-        if originator in [self._departureBriefingPage, self._takeoffPage]:
-            self._departureMETARChanged(metar, originator)
-        else:
-            self._arrivalMETARChanged(metar, originator)
-
-    def _departureMETARChanged(self, metar, originator):
-        """Called when the departure METAR has been edited on one of the
-        pages.
-
-        originator is the page that originated the change. It will not be
-        called to set the METAR, while others will be."""
-        for page in [self._departureBriefingPage, self._takeoffPage]:
-            if page is not originator:
-                page.changeMETAR(metar)
-
-    def _arrivalMETARChanged(self, metar, originator):
-        """Called when the arrival METAR has been edited on one of the
-        pages.
-
-        originator is the page that originated the change. It will not be
-        called to set the METAR, while others will be."""
-        for page in [self._arrivalBriefingPage, self._landingPage]:
-            if page is not originator:
-                page.changeMETAR(metar)
-
-    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.gui.endBusy()
-        returned = True
-        isReload = self._loginResult is not None
-        if returned:
-            if result.loggedIn:
-                self._loginResult = result
-            else:
-                if isReload:
-                    message = xstr("reload_failed")
-                else:
-                    message = xstr("login_entranceExam_invalid"
-                                   if self.entranceExam else
-                                   xstr("login_invalid"))
-                dialog = gtk.MessageDialog(parent = self.gui.mainWindow,
-                                           type = MESSAGETYPE_ERROR,
-                                           message_format = message)
-                dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-                dialog.set_title(WINDOW_TITLE_BASE)
-                if isReload:
-                    secondary = xstr("reload_failed_sec")
-                else:
-                    secondary = xstr("login_entranceExam_invalid_sec"
-                                     if self.entranceExam else
-                                     xstr("login_invalid_sec"))
-                dialog.format_secondary_markup(secondary)
-                dialog.run()
-                dialog.hide()
-        else:
-            message = xstr("reload_failconn") if isReload \
-                      else xstr("login_failconn")
-            dialog = gtk.MessageDialog(parent = self.gui.mainWindow,
-                                       type = MESSAGETYPE_ERROR,
-                                       message_format = message)
-            dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-            dialog.set_title(WINDOW_TITLE_BASE)
-            secondary = xstr("reload_failconn_sec") if isReload \
-                        else xstr("login_failconn_sec")
-            dialog.format_secondary_markup(secondary)
-
-            dialog.run()
-            dialog.hide()
-
-        callback = self._loginCallback
-        self._loginCallback = None
-        callback(returned, result)
-
-    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 updateRTO(self):
-        """Update the RTO state.
-
-        The RTO checkbox will be enabled if the flight has an RTO state and the
-        comments field contains some text."""
-        flight = self.gui.flight
-        rtoEnabled = flight is not None and flight.hasRTO and \
-                     self.gui.hasComments
-        self._takeoffPage.setRTOEnabled(rtoEnabled)
-
-    def commentsChanged(self):
-        """Called when the comments have changed."""
-        self.updateRTO()
-        self._finishPage.updateButtons()
-
-    def delayCodesChanged(self):
-        """Called when the delay codes have changed."""
-        self._finishPage.updateButtons()
-
-    def faultExplanationsChanged(self):
-        """Called when the faults and their explanations have changed."""
-        self._finishPage.updateButtons()
-
-    def rtoToggled(self, indicated):
-        """Called when the RTO indication has changed."""
-        self.gui.rtoToggled(indicated)
-
-    def _connectSimulator(self, simulatorType):
-        """Connect to the simulator."""
-        self.gui.connectSimulator(self._bookedFlight.aircraftType,
-                                  simulatorType)
-
-    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 619)
+++ src/mlx/gui/gates.py	(revision 619)
@@ -0,0 +1,183 @@
+
+from mlx.gui.common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+from mlx.gates import Gates, lhbpGates
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.gates
+#
+# The gate status display.
+#
+# This module contains the \ref FleetGateStatus widget, which is the tab
+# displaying the status of the MAVA Fleet and the gates at LHBP. The left side
+# of the widget is the table with the list of the planes and their states. The
+# right side displays the numbers of the gates and their occupation status.
+
+#-------------------------------------------------------------------------------
+
+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(lhbpGates.numRows,
+                                             lhbpGates.numColumns)
+        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 (type, data) in lhbpGates.displayInfos:
+            if type==Gates.DISPLAY_GATE:
+                gate = data
+
+                label = gtk.Label()
+                label.set_markup("<b>" + gate.number + "</b>")
+                table.attach(label, column, column + 1, row, row + 1)
+
+                self._gateLabels[gate.number] = label
+                row += 1
+            elif type==Gates.DISPLAY_SPACE:
+                row += 1
+            elif type==Gates.DISPLAY_NEW_COLUMN:
+                row = 0
+                column += 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 gate in lhbpGates.gates:
+                gateNumber = gate.number
+                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 619)
+++ 	(revision )
@@ -1,183 +1,0 @@
-
-from mlx.gui.common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-from mlx.gates import Gates, lhbpGates
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.gates
-#
-# The gate status display.
-#
-# This module contains the \ref FleetGateStatus widget, which is the tab
-# displaying the status of the MAVA Fleet and the gates at LHBP. The left side
-# of the widget is the table with the list of the planes and their states. The
-# right side displays the numbers of the gates and their occupation status.
-
-#-------------------------------------------------------------------------------
-
-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(lhbpGates.numRows,
-                                             lhbpGates.numColumns)
-        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 (type, data) in lhbpGates.displayInfos:
-            if type==Gates.DISPLAY_GATE:
-                gate = data
-
-                label = gtk.Label()
-                label.set_markup("<b>" + gate.number + "</b>")
-                table.attach(label, column, column + 1, row, row + 1)
-
-                self._gateLabels[gate.number] = label
-                row += 1
-            elif type==Gates.DISPLAY_SPACE:
-                row += 1
-            elif type==Gates.DISPLAY_NEW_COLUMN:
-                row = 0
-                column += 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 gate in lhbpGates.gates:
-                gateNumber = gate.number
-                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 687)
+++ src/mlx/gui/gui.py	(revision 687)
@@ -0,0 +1,1636 @@
+# -*- coding: utf-8 -*-
+
+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
+from mlx.gui.checklist import ChecklistEditor
+from mlx.gui.callouts import ApproachCalloutsEditor
+from mlx.gui.pirep import PIREPViewer
+from mlx.gui.bugreport import BugReportDialog
+from mlx.gui.acars import ACARS
+import cef
+
+import mlx.const as const
+import mlx.fs as fs
+import mlx.flight as flight
+import mlx.logger as logger
+import mlx.acft as acft
+import mlx.web as web
+import mlx.singleton as singleton
+import mlx.airports as airports
+from  mlx.i18n import xstr, getLanguage
+from mlx.pirep import PIREP
+
+import time
+import threading
+import sys
+import datetime
+import webbrowser
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.gui
+#
+# The main GUI class.
+#
+# The \ref GUI class is the main class of the GUI. It is a connection listener,
+# and aggregates all the windows, the menu, etc. It maintains the connection to
+# the simulator as well as the flight object.
+
+#------------------------------------------------------------------------------
+
+class GUI(fs.ConnectionListener):
+    """The main GUI class."""
+    _authors = [ (u"Váradi", u"István", "prog_test"),
+                 (u"Galyassy", u"Tamás", "negotiation"),
+                 (u"Kurják", u"Ákos", "test"),
+                 (u"Nagy", u"Dániel", "test"),
+                 (u"Radó", u"Iván", "test"),
+                 (u"Petrovszki", u"Gábor", "test"),
+                 (u"Serfőző", u"Tamás", "test"),
+                 (u"Szebenyi", u"Bálint", "test"),
+                 (u"Zsebényi-Loksa", u"Gergely", "test") ]
+
+    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._fsType = 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._stdioStartingLine = True
+
+        self._sendPIREPCallback = None
+        self._sendBugReportCallback = 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.set_resizable(False)
+        window.connect("delete-event", self.deleteMainWindow)
+        window.connect("window-state-event", self._handleMainWindowState)
+        if os.name=="nt":
+            window.connect("leave-notify-event", self._handleLeaveNotify)
+        accelGroup = gtk.AccelGroup()
+        window.add_accel_group(accelGroup)
+        window.realize()
+
+        mainVBox = gtk.VBox()
+        window.add(mainVBox)
+
+        self._preferences = Preferences(self)
+        self._checklistEditor = ChecklistEditor(self)
+        self._approachCalloutsEditor = ApproachCalloutsEditor(self)
+        self._bugReportDialog = BugReportDialog(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()
+        addFaultTag(self._logView.get_buffer())
+        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._acars = ACARS(self)
+        label = gtk.Label("ACARS")
+        label.set_use_underline(True)
+        self._notebook.append_page(self._acars, label)
+
+        (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
+        self._debugLogWidget.show_all()
+
+        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
+
+        self._statusbar = Statusbar(iconDirectory)
+        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
+
+        self._pirepViewer = PIREPViewer(self)
+
+        window.show_all()
+        if os.name=="nt":
+            window.get_window().focus()
+
+        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
+
+        self._hotkeySetID = None
+        self._pilotHotkeyIndex = None
+        self._checklistHotkeyIndex = None
+
+        self._aboutDialog = 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 fsType(self):
+        """Get the flight simulator type."""
+        return self._fsType
+
+    @property
+    def entranceExam(self):
+        """Get whether an entrance exam is about to be taken."""
+        return self._wizard.entranceExam
+
+    @property
+    def loggedIn(self):
+        """Indicate if the user has logged in properly."""
+        return self._wizard.loggedIn
+
+    @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 numCrew(self):
+        """Get the number of crew members."""
+        return self._wizard.numCrew
+
+    @property
+    def numPassengers(self):
+        """Get the number of passengers."""
+        return self._wizard.numPassengers
+
+    @property
+    def bagWeight(self):
+        """Get the bag weight."""
+        return self._wizard.bagWeight
+
+    @property
+    def cargoWeight(self):
+        """Get the cargo weight."""
+        return self._wizard.cargoWeight
+
+    @property
+    def mailWeight(self):
+        """Get the mail weight."""
+        return self._wizard.mailWeight
+
+    @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 loggableCruiseAltitude(self):
+        """Get the cruise altitude that can be logged."""
+        return self._wizard.loggableCruiseAltitude
+
+    @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 derate(self):
+        """Get the derate value calculated for the flight."""
+        return self._wizard.derate
+
+    @property
+    def takeoffAntiIceOn(self):
+        """Get whether the anti-ice system was on during take-off."""
+        return self._wizard.takeoffAntiIceOn
+
+    @takeoffAntiIceOn.setter
+    def takeoffAntiIceOn(self, value):
+        """Set the anti-ice on indicator."""
+        gobject.idle_add(self._setTakeoffAntiIceOn, value)
+
+    @property
+    def rtoIndicated(self):
+        """Get whether the pilot has indicated than an RTO has occured."""
+        return self._wizard.rtoIndicated
+
+    @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 landingAntiIceOn(self):
+        """Get whether the anti-ice system was on during landing."""
+        return self._wizard.landingAntiIceOn
+
+    @landingAntiIceOn.setter
+    def landingAntiIceOn(self, value):
+        """Set the anti-ice on indicator."""
+        gobject.idle_add(self._setLandingAntiIceOn, value)
+
+    @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 hasComments(self):
+        """Indicate whether there is a comment."""
+        return self._flightInfo.hasComments
+
+    @property
+    def flightDefects(self):
+        """Get the flight defects."""
+        return self._flightInfo.faultsAndExplanations
+
+    @property
+    def delayCodes(self):
+        """Get the delay codes."""
+        return self._flightInfo.delayCodes
+
+    @property
+    def hasDelayCode(self):
+        """Determine if there is at least one delay code selected."""
+        return self._flightInfo.hasDelayCode
+
+    @property
+    def faultsFullyExplained(self):
+        """Determine if all the faults have been fully explained by the
+        user."""
+        return self._flightInfo.faultsFullyExplained
+
+    if pygobject:
+        @property
+        def backgroundColour(self):
+            """Get the background colour of the main window."""
+            return self._mainWindow.get_style_context().\
+              get_background_color(gtk.StateFlags.NORMAL)
+
+    def run(self):
+        """Run the GUI."""
+        if self.config.autoUpdate:
+            self._updater = Updater(self,
+                                    self._programDirectory,
+                                    self.config.DEFAULT_UPDATE_URL,
+                                    self._mainWindow)
+            self._updater.start()
+
+        cef.initialize(self._programDirectory, self._cefInitialized)
+
+        singleton.raiseCallback = self.raiseCallback
+        gtk.main()
+        singleton.raiseCallback = None
+
+        cef.finalize()
+
+        self._disconnect()
+
+    def connected(self, fsType, descriptor):
+        """Called when we have connected to the simulator."""
+        self._connected = True
+        self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
+                                    (const.VERSION, 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
+        self._fsType = fsType
+        self._listenHotkeys()
+
+    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")
+        if self._flight is not None:
+            self._flight.disconnected()
+
+        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, aircraftType):
+        """Enable the flight info tab."""
+        self._flightInfo.enable(aircraftType)
+
+    def cancelFlight(self):
+        """Cancel the current file, if the user confirms it."""
+        dialog = gtk.MessageDialog(parent = self._mainWindow,
+                                   type = MESSAGETYPE_QUESTION,
+                                   message_format = xstr("cancelFlight_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.reset()
+
+    def reset(self):
+        """Reset the GUI."""
+        self._disconnect()
+
+        self._simulator = None
+
+        self._flightInfo.reset()
+        self._flightInfo.disable()
+        self.resetFlightStatus()
+
+        self._weightHelp.reset()
+        self._weightHelp.disable()
+        self._notebook.set_current_page(0)
+
+        self._logView.get_buffer().set_text("")
+
+        if self.loggedIn:
+            self._wizard.reloadFlights(self._handleReloadResult)
+        else:
+            self._wizard.reset(None)
+
+    def _handleReloadResult(self, returned, result):
+        """Handle the result of the reloading of the flights."""
+        self._wizard.reset(result if returned and result.loggedIn else None)
+
+    def _disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator if connected."""
+        self.stopMonitoring()
+        self._clearHotkeys()
+
+        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 insertFlightLogLine(self, index, timestampString, text, isFault):
+        """Insert the flight log line with the given data."""
+        gobject.idle_add(self._insertFlightLogLine, index,
+                         formatFlightLogLine(timestampString, text),
+                         isFault)
+
+    def _insertFlightLogLine(self, index, line, isFault):
+        """Perform the real insertion.
+
+        To be called from the event loop."""
+        buffer = self._logView.get_buffer()
+        lineIter = buffer.get_iter_at_line(index)
+        insertTextBuffer(buffer, lineIter, line, isFault = isFault)
+        self._logView.scroll_mark_onscreen(buffer.get_insert())
+
+    def removeFlightLogLine(self, index):
+        """Remove the flight log line with the given index."""
+        gobject.idle_add(self._removeFlightLogLine, index)
+
+    def addFault(self, id, timestampString, text):
+        """Add a fault to the list of faults."""
+        faultText = formatFlightLogLine(timestampString, text).strip()
+        gobject.idle_add(self._flightInfo.addFault, id, faultText)
+
+    def updateFault(self, id, timestampString, text):
+        """Update a fault in the list of faults."""
+        faultText = formatFlightLogLine(timestampString, text).strip()
+        gobject.idle_add(self._flightInfo.updateFault, id, faultText)
+
+    def clearFault(self, id):
+        """Clear a fault in the list of faults."""
+        gobject.idle_add(self._flightInfo.clearFault, id)
+
+    def _removeFlightLogLine(self, index):
+        """Perform the real removal."""
+        buffer = self._logView.get_buffer()
+        startIter = buffer.get_iter_at_line(index)
+        endIter = buffer.get_iter_at_line(index+1)
+        buffer.delete(startIter, endIter)
+        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:
+            welcomeMessage = \
+                airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
+            self._disconnect(closingMessage =
+                             "Flight plan closed. " + welcomeMessage,
+                             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 (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
+            if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
+                self._statusIcon.mainWindowHidden()
+            else:
+                self._statusIcon.mainWindowShown()
+
+        if self.config.hideMinimizedWindow and not pygobject and \
+           (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
+           (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
+            self.hideMainWindow(savePosition = False)
+        elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
+             (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
+            self._mainWindow.present()
+
+    def _handleLeaveNotify(self, widget, event):
+        """Handle the leave-notify event.
+
+        Here we reset the focus to the main window as CEF might have acquired
+        it earlier."""
+        self._mainWindow.get_window().focus()
+
+    def raiseCallback(self):
+        """Callback for the singleton handling code."""
+        gobject.idle_add(self.raiseMainWindow)
+
+    def raiseMainWindow(self):
+        """Show the main window if invisible, and raise it."""
+        if not self._mainWindow.get_visible():
+            self.showMainWindow()
+        self._mainWindow.present()
+
+    def deleteMainWindow(self, window, event):
+        """Handle the delete event for the main window."""
+        if self.config.quitOnClose:
+            self._quit()
+        else:
+            self.hideMainWindow()
+        return True
+
+    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()
+        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)
+
+        if pygobject:
+            self._mainWindow.show()
+        else:
+            self._mainWindow.present()
+        self._mainWindow.deiconify()
+
+    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 updateBusyState(self, message):
+        """Update the busy state."""
+        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 commentsChanged(self):
+        """Indicate that the comments have changed."""
+        self._wizard.commentsChanged()
+
+    def delayCodesChanged(self):
+        """Called when the delay codes have changed."""
+        self._wizard.delayCodesChanged()
+
+    def faultExplanationsChanged(self):
+        """Called when the status of the explanations of the faults have
+        changed."""
+        self._wizard.faultExplanationsChanged()
+
+    def updateRTO(self, inLoop = False):
+        """Indicate that the RTO state should be updated."""
+        if inLoop:
+            self._wizard.updateRTO()
+        else:
+            gobject.idle_add(self.updateRTO, True)
+
+    def rtoToggled(self, indicated):
+        """Called when the user has toggled the RTO checkbox."""
+        self._flight.rtoToggled(indicated)
+
+    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]
+
+        now = datetime.datetime.now()
+        timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
+
+        for line in lines:
+            #print >> sys.__stdout__, line
+            if self._stdioStartingLine:
+                self._writeLog(timeStr, self._debugLogView)
+            self._writeLog(line + "\n", self._debugLogView)
+            self._stdioStartingLine = True
+
+        if text:
+            #print >> sys.__stdout__, text,
+            if self._stdioStartingLine:
+                self._writeLog(timeStr, self._debugLogView)
+            self._writeLog(text, self._debugLogView)
+            self._stdioStartingLine = False
+
+    def connectSimulator(self, aircraftType, simulatorType):
+        """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(simulatorType, 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 cruiseLevelChanged(self):
+        """Called when the cruise level is changed in the flight wizard."""
+        if self._flight is not None:
+            return self._flight.cruiseLevelChanged()
+        else:
+            return 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)
+
+        checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
+        checklistMenuItem.set_use_stock(True)
+        checklistMenuItem.set_label(xstr("menu_tools_chklst"))
+        checklistMenuItem.add_accelerator("activate", accelGroup,
+                                          ord(xstr("menu_tools_chklst_key")),
+                                          CONTROL_MASK, ACCEL_VISIBLE)
+        checklistMenuItem.connect("activate", self._editChecklist)
+        toolsMenu.append(checklistMenuItem)
+
+        approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
+        approachCalloutsMenuItem.set_use_stock(True)
+        approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
+        approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
+                                                 ord(xstr("menu_tools_callouts_key")),
+                                                 CONTROL_MASK, ACCEL_VISIBLE)
+        approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
+        toolsMenu.append(approachCalloutsMenuItem)
+
+        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)
+
+        toolsMenu.append(gtk.SeparatorMenuItem())
+
+        bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
+        bugReportMenuItem.set_use_stock(True)
+        bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
+        bugReportMenuItem.add_accelerator("activate", accelGroup,
+                                          ord(xstr("menu_tools_bugreport_key")),
+                                          CONTROL_MASK, ACCEL_VISIBLE)
+        bugReportMenuItem.connect("activate", self._reportBug)
+        toolsMenu.append(bugReportMenuItem)
+
+        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)
+
+        helpMenuItem = gtk.MenuItem(xstr("menu_help"))
+        helpMenu = gtk.Menu()
+        helpMenuItem.set_submenu(helpMenu)
+        menuBar.append(helpMenuItem)
+
+        manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
+        manualMenuItem.set_use_stock(True)
+        manualMenuItem.set_label(xstr("menu_help_manual"))
+        manualMenuItem.add_accelerator("activate", accelGroup,
+                                       ord(xstr("menu_help_manual_key")),
+                                       CONTROL_MASK, ACCEL_VISIBLE)
+        manualMenuItem.connect("activate", self._showManual)
+        helpMenu.append(manualMenuItem)
+
+        helpMenu.append(gtk.SeparatorMenuItem())
+
+        aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
+        aboutMenuItem.set_use_stock(True)
+        aboutMenuItem.set_label(xstr("menu_help_about"))
+        aboutMenuItem.add_accelerator("activate", accelGroup,
+                                      ord(xstr("menu_help_about_key")),
+                                      CONTROL_MASK, ACCEL_VISIBLE)
+        aboutMenuItem.connect("activate", self._showAbout)
+        helpMenu.append(aboutMenuItem)
+
+        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)
+        logView.set_cursor_visible(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, isFault = False):
+        """Write the given message to the log."""
+        buffer = logView.get_buffer()
+        appendTextBuffer(buffer, msg, isFault = isFault)
+        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 isWizardActive(self):
+        """Determine if the flight wizard is active."""
+        return self._notebook.get_current_page()==0
+
+    def _editChecklist(self, menuItem):
+        """Callback for editing the checklists."""
+        self._checklistEditor.run()
+
+    def _editApproachCallouts(self, menuItem):
+        """Callback for editing the approach callouts."""
+        self._approachCalloutsEditor.run()
+
+    def _editPreferences(self, menuItem):
+        """Callback for editing the preferences."""
+        self._clearHotkeys()
+        self._preferences.run(self.config)
+        self._setupTimeSync()
+        self._listenHotkeys()
+
+    def _reportBug(self, menuItem):
+        """Callback for reporting a bug."""
+        self._bugReportDialog.run()
+
+    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()
+                while True:
+                    result = dialog.run()
+
+                    if result==RESPONSETYPE_OK:
+                        self.sendPIREP(pirep)
+                    elif result==1:
+                        self._pirepViewer.setPIREP(pirep)
+                        self._pirepViewer.show_all()
+                        self._pirepViewer.run()
+                        self._pirepViewer.hide()
+                    else:
+                        break
+
+                dialog.hide()
+
+    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("file_filter_pireps"))
+            filter.add_pattern("*.pirep")
+            dialog.add_filter(filter)
+
+            filter = gtk.FileFilter()
+            filter.set_name(xstr("file_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("viewPIREP"), 1)
+        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)
+
+    def sendBugReport(self, summary, description, email, callback = None):
+        """Send the bug report with the given data."""
+        description += "\n\n" + ("=" * 40)
+        description += "\n\nThe contents of the log:\n\n"
+
+        for (timestampString, text) in self._logger.lines:
+            description += unicode(formatFlightLogLine(timestampString, text))
+
+        description += "\n\n" + ("=" * 40)
+        description += "\n\nThe contents of the debug log:\n\n"
+
+        buffer = self._debugLogView.get_buffer()
+        description += buffer.get_text(buffer.get_start_iter(),
+                                       buffer.get_end_iter(), True)
+
+        self.beginBusy(xstr("sendBugReport_busy"))
+        self._sendBugReportCallback = callback
+        self.webHandler.sendBugReport(self._bugReportSentCallback,
+                                      summary, description, email)
+
+    def _cefInitialized(self):
+        """Called when CEF has been initialized."""
+        self._acars.start()
+        cef.initializeSimBrief()
+
+    def _bugReportSentCallback(self, returned, result):
+        """Callback function for the bug report sending result."""
+        gobject.idle_add(self._handleBugReportSent, returned, result)
+
+    def _handleBugReportSent(self, returned, result):
+        """Callback for the bug report sending result."""
+        self.endBusy()
+        secondaryMarkup = None
+        type = MESSAGETYPE_ERROR
+        if returned:
+            if result.success:
+                type = MESSAGETYPE_INFO
+                messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
+                secondaryMarkup = xstr("sendBugReport_success_sec")
+            else:
+                messageFormat = xstr("sendBugReport_error")
+                secondaryMarkup = xstr("sendBugReport_siteerror_sec")
+        else:
+            messageFormat = xstr("sendBugReport_error")
+            secondaryMarkup = xstr("sendBugReport_error_sec")
+
+        dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
+                                   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._sendBugReportCallback
+        self._sendBugReportCallback = None
+        if callback is not None:
+            callback(returned, result)
+
+    def _listenHotkeys(self):
+        """Setup the hotkeys based on the configuration."""
+        if self._hotkeySetID is None and self._simulator is not None:
+            self._pilotHotkeyIndex = None
+            self._checklistHotkeyIndex = None
+
+            hotkeys = []
+
+            config = self.config
+            if config.enableSounds and config.pilotControlsSounds:
+                self._pilotHotkeyIndex = len(hotkeys)
+                hotkeys.append(config.pilotHotkey)
+
+            if config.enableChecklists:
+                self._checklistHotkeyIndex = len(hotkeys)
+                hotkeys.append(config.checklistHotkey)
+
+            if hotkeys:
+                self._hotkeySetID = \
+                    self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
+
+    def _clearHotkeys(self):
+        """Clear the hotkeys."""
+        if self._hotkeySetID is not None:
+            self._hotkeySetID=None
+            self._simulator.clearHotkeys()
+
+    def _handleHotkeys(self, id, hotkeys):
+        """Handle the hotkeys."""
+        if id==self._hotkeySetID:
+            for index in hotkeys:
+                if index==self._pilotHotkeyIndex:
+                    print "gui.GUI._handleHotkeys: pilot hotkey pressed"
+                    self._flight.pilotHotkeyPressed()
+                elif index==self._checklistHotkeyIndex:
+                    print "gui.GUI._handleHotkeys: checklist hotkey pressed"
+                    self._flight.checklistHotkeyPressed()
+                else:
+                    print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
+
+    def _showManual(self, menuitem):
+        """Show the user's manual."""
+        webbrowser.open(url ="file://" +
+                        os.path.join(self._programDirectory, "doc", "manual",
+                                     getLanguage(), "index.html"),
+                        new = 1)
+
+    def _showAbout(self, menuitem):
+        """Show the about dialog."""
+        dialog = self._getAboutDialog()
+        dialog.show_all()
+        dialog.run()
+        dialog.hide()
+
+    def _getAboutDialog(self):
+        """Get the about dialog.
+
+        If it does not exist yet, it will be created."""
+        if self._aboutDialog is None:
+            dialog = gtk.AboutDialog()
+            dialog.set_transient_for(self._mainWindow)
+            dialog.set_modal(True)
+
+            logoPath = os.path.join(self._programDirectory, "logo.png")
+            logo = pixbuf_new_from_file(logoPath)
+            dialog.set_logo(logo)
+
+            dialog.set_program_name(PROGRAM_NAME)
+            dialog.set_version(const.VERSION)
+            dialog.set_copyright("(c) 2012 by István Váradi")
+            dialog.set_website("http://mlx.varadiistvan.hu")
+            dialog.set_website_label(xstr("about_website"))
+
+            isHungarian = getLanguage()=="hu"
+            authors = []
+            for (familyName, firstName, role) in GUI._authors:
+                author = "%s %s" % \
+                         (familyName if isHungarian else firstName,
+                          firstName if isHungarian else familyName)
+                role = xstr("about_role_" + role)
+                authors.append(author + " (" + role + ")")
+            dialog.set_authors(authors)
+
+            dialog.set_license(xstr("about_license"))
+
+            if not pygobject:
+                gtk.about_dialog_set_url_hook(self._showAboutURL, None)
+
+            self._aboutDialog = dialog
+
+        return self._aboutDialog
+
+    def _showAboutURL(self, dialog, link, user_data):
+        """Show the about URL."""
+        webbrowser.open(url = link, new = 1)
+
+    def _setTakeoffAntiIceOn(self, value):
+        """Set the anti-ice on indicator."""
+        self._wizard.takeoffAntiIceOn = value
+
+    def _setLandingAntiIceOn(self, value):
+        """Set the anti-ice on indicator."""
+        self._wizard.landingAntiIceOn = value
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 687)
+++ 	(revision )
@@ -1,1636 +1,0 @@
-# -*- coding: utf-8 -*-
-
-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
-from mlx.gui.checklist import ChecklistEditor
-from mlx.gui.callouts import ApproachCalloutsEditor
-from mlx.gui.pirep import PIREPViewer
-from mlx.gui.bugreport import BugReportDialog
-from mlx.gui.acars import ACARS
-import cef
-
-import mlx.const as const
-import mlx.fs as fs
-import mlx.flight as flight
-import mlx.logger as logger
-import mlx.acft as acft
-import mlx.web as web
-import mlx.singleton as singleton
-import mlx.airports as airports
-from  mlx.i18n import xstr, getLanguage
-from mlx.pirep import PIREP
-
-import time
-import threading
-import sys
-import datetime
-import webbrowser
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.gui
-#
-# The main GUI class.
-#
-# The \ref GUI class is the main class of the GUI. It is a connection listener,
-# and aggregates all the windows, the menu, etc. It maintains the connection to
-# the simulator as well as the flight object.
-
-#------------------------------------------------------------------------------
-
-class GUI(fs.ConnectionListener):
-    """The main GUI class."""
-    _authors = [ (u"Váradi", u"István", "prog_test"),
-                 (u"Galyassy", u"Tamás", "negotiation"),
-                 (u"Kurják", u"Ákos", "test"),
-                 (u"Nagy", u"Dániel", "test"),
-                 (u"Radó", u"Iván", "test"),
-                 (u"Petrovszki", u"Gábor", "test"),
-                 (u"Serfőző", u"Tamás", "test"),
-                 (u"Szebenyi", u"Bálint", "test"),
-                 (u"Zsebényi-Loksa", u"Gergely", "test") ]
-
-    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._fsType = 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._stdioStartingLine = True
-
-        self._sendPIREPCallback = None
-        self._sendBugReportCallback = 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.set_resizable(False)
-        window.connect("delete-event", self.deleteMainWindow)
-        window.connect("window-state-event", self._handleMainWindowState)
-        if os.name=="nt":
-            window.connect("leave-notify-event", self._handleLeaveNotify)
-        accelGroup = gtk.AccelGroup()
-        window.add_accel_group(accelGroup)
-        window.realize()
-
-        mainVBox = gtk.VBox()
-        window.add(mainVBox)
-
-        self._preferences = Preferences(self)
-        self._checklistEditor = ChecklistEditor(self)
-        self._approachCalloutsEditor = ApproachCalloutsEditor(self)
-        self._bugReportDialog = BugReportDialog(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()
-        addFaultTag(self._logView.get_buffer())
-        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._acars = ACARS(self)
-        label = gtk.Label("ACARS")
-        label.set_use_underline(True)
-        self._notebook.append_page(self._acars, label)
-
-        (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
-        self._debugLogWidget.show_all()
-
-        mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
-
-        self._statusbar = Statusbar(iconDirectory)
-        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
-
-        self._pirepViewer = PIREPViewer(self)
-
-        window.show_all()
-        if os.name=="nt":
-            window.get_window().focus()
-
-        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
-
-        self._hotkeySetID = None
-        self._pilotHotkeyIndex = None
-        self._checklistHotkeyIndex = None
-
-        self._aboutDialog = 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 fsType(self):
-        """Get the flight simulator type."""
-        return self._fsType
-
-    @property
-    def entranceExam(self):
-        """Get whether an entrance exam is about to be taken."""
-        return self._wizard.entranceExam
-
-    @property
-    def loggedIn(self):
-        """Indicate if the user has logged in properly."""
-        return self._wizard.loggedIn
-
-    @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 numCrew(self):
-        """Get the number of crew members."""
-        return self._wizard.numCrew
-
-    @property
-    def numPassengers(self):
-        """Get the number of passengers."""
-        return self._wizard.numPassengers
-
-    @property
-    def bagWeight(self):
-        """Get the bag weight."""
-        return self._wizard.bagWeight
-
-    @property
-    def cargoWeight(self):
-        """Get the cargo weight."""
-        return self._wizard.cargoWeight
-
-    @property
-    def mailWeight(self):
-        """Get the mail weight."""
-        return self._wizard.mailWeight
-
-    @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 loggableCruiseAltitude(self):
-        """Get the cruise altitude that can be logged."""
-        return self._wizard.loggableCruiseAltitude
-
-    @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 derate(self):
-        """Get the derate value calculated for the flight."""
-        return self._wizard.derate
-
-    @property
-    def takeoffAntiIceOn(self):
-        """Get whether the anti-ice system was on during take-off."""
-        return self._wizard.takeoffAntiIceOn
-
-    @takeoffAntiIceOn.setter
-    def takeoffAntiIceOn(self, value):
-        """Set the anti-ice on indicator."""
-        gobject.idle_add(self._setTakeoffAntiIceOn, value)
-
-    @property
-    def rtoIndicated(self):
-        """Get whether the pilot has indicated than an RTO has occured."""
-        return self._wizard.rtoIndicated
-
-    @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 landingAntiIceOn(self):
-        """Get whether the anti-ice system was on during landing."""
-        return self._wizard.landingAntiIceOn
-
-    @landingAntiIceOn.setter
-    def landingAntiIceOn(self, value):
-        """Set the anti-ice on indicator."""
-        gobject.idle_add(self._setLandingAntiIceOn, value)
-
-    @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 hasComments(self):
-        """Indicate whether there is a comment."""
-        return self._flightInfo.hasComments
-
-    @property
-    def flightDefects(self):
-        """Get the flight defects."""
-        return self._flightInfo.faultsAndExplanations
-
-    @property
-    def delayCodes(self):
-        """Get the delay codes."""
-        return self._flightInfo.delayCodes
-
-    @property
-    def hasDelayCode(self):
-        """Determine if there is at least one delay code selected."""
-        return self._flightInfo.hasDelayCode
-
-    @property
-    def faultsFullyExplained(self):
-        """Determine if all the faults have been fully explained by the
-        user."""
-        return self._flightInfo.faultsFullyExplained
-
-    if pygobject:
-        @property
-        def backgroundColour(self):
-            """Get the background colour of the main window."""
-            return self._mainWindow.get_style_context().\
-              get_background_color(gtk.StateFlags.NORMAL)
-
-    def run(self):
-        """Run the GUI."""
-        if self.config.autoUpdate:
-            self._updater = Updater(self,
-                                    self._programDirectory,
-                                    self.config.DEFAULT_UPDATE_URL,
-                                    self._mainWindow)
-            self._updater.start()
-
-        cef.initialize(self._programDirectory, self._cefInitialized)
-
-        singleton.raiseCallback = self.raiseCallback
-        gtk.main()
-        singleton.raiseCallback = None
-
-        cef.finalize()
-
-        self._disconnect()
-
-    def connected(self, fsType, descriptor):
-        """Called when we have connected to the simulator."""
-        self._connected = True
-        self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
-                                    (const.VERSION, 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
-        self._fsType = fsType
-        self._listenHotkeys()
-
-    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")
-        if self._flight is not None:
-            self._flight.disconnected()
-
-        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, aircraftType):
-        """Enable the flight info tab."""
-        self._flightInfo.enable(aircraftType)
-
-    def cancelFlight(self):
-        """Cancel the current file, if the user confirms it."""
-        dialog = gtk.MessageDialog(parent = self._mainWindow,
-                                   type = MESSAGETYPE_QUESTION,
-                                   message_format = xstr("cancelFlight_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.reset()
-
-    def reset(self):
-        """Reset the GUI."""
-        self._disconnect()
-
-        self._simulator = None
-
-        self._flightInfo.reset()
-        self._flightInfo.disable()
-        self.resetFlightStatus()
-
-        self._weightHelp.reset()
-        self._weightHelp.disable()
-        self._notebook.set_current_page(0)
-
-        self._logView.get_buffer().set_text("")
-
-        if self.loggedIn:
-            self._wizard.reloadFlights(self._handleReloadResult)
-        else:
-            self._wizard.reset(None)
-
-    def _handleReloadResult(self, returned, result):
-        """Handle the result of the reloading of the flights."""
-        self._wizard.reset(result if returned and result.loggedIn else None)
-
-    def _disconnect(self, closingMessage = None, duration = 3):
-        """Disconnect from the simulator if connected."""
-        self.stopMonitoring()
-        self._clearHotkeys()
-
-        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 insertFlightLogLine(self, index, timestampString, text, isFault):
-        """Insert the flight log line with the given data."""
-        gobject.idle_add(self._insertFlightLogLine, index,
-                         formatFlightLogLine(timestampString, text),
-                         isFault)
-
-    def _insertFlightLogLine(self, index, line, isFault):
-        """Perform the real insertion.
-
-        To be called from the event loop."""
-        buffer = self._logView.get_buffer()
-        lineIter = buffer.get_iter_at_line(index)
-        insertTextBuffer(buffer, lineIter, line, isFault = isFault)
-        self._logView.scroll_mark_onscreen(buffer.get_insert())
-
-    def removeFlightLogLine(self, index):
-        """Remove the flight log line with the given index."""
-        gobject.idle_add(self._removeFlightLogLine, index)
-
-    def addFault(self, id, timestampString, text):
-        """Add a fault to the list of faults."""
-        faultText = formatFlightLogLine(timestampString, text).strip()
-        gobject.idle_add(self._flightInfo.addFault, id, faultText)
-
-    def updateFault(self, id, timestampString, text):
-        """Update a fault in the list of faults."""
-        faultText = formatFlightLogLine(timestampString, text).strip()
-        gobject.idle_add(self._flightInfo.updateFault, id, faultText)
-
-    def clearFault(self, id):
-        """Clear a fault in the list of faults."""
-        gobject.idle_add(self._flightInfo.clearFault, id)
-
-    def _removeFlightLogLine(self, index):
-        """Perform the real removal."""
-        buffer = self._logView.get_buffer()
-        startIter = buffer.get_iter_at_line(index)
-        endIter = buffer.get_iter_at_line(index+1)
-        buffer.delete(startIter, endIter)
-        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:
-            welcomeMessage = \
-                airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
-            self._disconnect(closingMessage =
-                             "Flight plan closed. " + welcomeMessage,
-                             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 (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
-            if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
-                self._statusIcon.mainWindowHidden()
-            else:
-                self._statusIcon.mainWindowShown()
-
-        if self.config.hideMinimizedWindow and not pygobject and \
-           (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
-           (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
-            self.hideMainWindow(savePosition = False)
-        elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
-             (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
-            self._mainWindow.present()
-
-    def _handleLeaveNotify(self, widget, event):
-        """Handle the leave-notify event.
-
-        Here we reset the focus to the main window as CEF might have acquired
-        it earlier."""
-        self._mainWindow.get_window().focus()
-
-    def raiseCallback(self):
-        """Callback for the singleton handling code."""
-        gobject.idle_add(self.raiseMainWindow)
-
-    def raiseMainWindow(self):
-        """Show the main window if invisible, and raise it."""
-        if not self._mainWindow.get_visible():
-            self.showMainWindow()
-        self._mainWindow.present()
-
-    def deleteMainWindow(self, window, event):
-        """Handle the delete event for the main window."""
-        if self.config.quitOnClose:
-            self._quit()
-        else:
-            self.hideMainWindow()
-        return True
-
-    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()
-        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)
-
-        if pygobject:
-            self._mainWindow.show()
-        else:
-            self._mainWindow.present()
-        self._mainWindow.deiconify()
-
-    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 updateBusyState(self, message):
-        """Update the busy state."""
-        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 commentsChanged(self):
-        """Indicate that the comments have changed."""
-        self._wizard.commentsChanged()
-
-    def delayCodesChanged(self):
-        """Called when the delay codes have changed."""
-        self._wizard.delayCodesChanged()
-
-    def faultExplanationsChanged(self):
-        """Called when the status of the explanations of the faults have
-        changed."""
-        self._wizard.faultExplanationsChanged()
-
-    def updateRTO(self, inLoop = False):
-        """Indicate that the RTO state should be updated."""
-        if inLoop:
-            self._wizard.updateRTO()
-        else:
-            gobject.idle_add(self.updateRTO, True)
-
-    def rtoToggled(self, indicated):
-        """Called when the user has toggled the RTO checkbox."""
-        self._flight.rtoToggled(indicated)
-
-    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]
-
-        now = datetime.datetime.now()
-        timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
-
-        for line in lines:
-            #print >> sys.__stdout__, line
-            if self._stdioStartingLine:
-                self._writeLog(timeStr, self._debugLogView)
-            self._writeLog(line + "\n", self._debugLogView)
-            self._stdioStartingLine = True
-
-        if text:
-            #print >> sys.__stdout__, text,
-            if self._stdioStartingLine:
-                self._writeLog(timeStr, self._debugLogView)
-            self._writeLog(text, self._debugLogView)
-            self._stdioStartingLine = False
-
-    def connectSimulator(self, aircraftType, simulatorType):
-        """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(simulatorType, 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 cruiseLevelChanged(self):
-        """Called when the cruise level is changed in the flight wizard."""
-        if self._flight is not None:
-            return self._flight.cruiseLevelChanged()
-        else:
-            return 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)
-
-        checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
-        checklistMenuItem.set_use_stock(True)
-        checklistMenuItem.set_label(xstr("menu_tools_chklst"))
-        checklistMenuItem.add_accelerator("activate", accelGroup,
-                                          ord(xstr("menu_tools_chklst_key")),
-                                          CONTROL_MASK, ACCEL_VISIBLE)
-        checklistMenuItem.connect("activate", self._editChecklist)
-        toolsMenu.append(checklistMenuItem)
-
-        approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
-        approachCalloutsMenuItem.set_use_stock(True)
-        approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
-        approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
-                                                 ord(xstr("menu_tools_callouts_key")),
-                                                 CONTROL_MASK, ACCEL_VISIBLE)
-        approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
-        toolsMenu.append(approachCalloutsMenuItem)
-
-        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)
-
-        toolsMenu.append(gtk.SeparatorMenuItem())
-
-        bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
-        bugReportMenuItem.set_use_stock(True)
-        bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
-        bugReportMenuItem.add_accelerator("activate", accelGroup,
-                                          ord(xstr("menu_tools_bugreport_key")),
-                                          CONTROL_MASK, ACCEL_VISIBLE)
-        bugReportMenuItem.connect("activate", self._reportBug)
-        toolsMenu.append(bugReportMenuItem)
-
-        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)
-
-        helpMenuItem = gtk.MenuItem(xstr("menu_help"))
-        helpMenu = gtk.Menu()
-        helpMenuItem.set_submenu(helpMenu)
-        menuBar.append(helpMenuItem)
-
-        manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
-        manualMenuItem.set_use_stock(True)
-        manualMenuItem.set_label(xstr("menu_help_manual"))
-        manualMenuItem.add_accelerator("activate", accelGroup,
-                                       ord(xstr("menu_help_manual_key")),
-                                       CONTROL_MASK, ACCEL_VISIBLE)
-        manualMenuItem.connect("activate", self._showManual)
-        helpMenu.append(manualMenuItem)
-
-        helpMenu.append(gtk.SeparatorMenuItem())
-
-        aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
-        aboutMenuItem.set_use_stock(True)
-        aboutMenuItem.set_label(xstr("menu_help_about"))
-        aboutMenuItem.add_accelerator("activate", accelGroup,
-                                      ord(xstr("menu_help_about_key")),
-                                      CONTROL_MASK, ACCEL_VISIBLE)
-        aboutMenuItem.connect("activate", self._showAbout)
-        helpMenu.append(aboutMenuItem)
-
-        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)
-        logView.set_cursor_visible(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, isFault = False):
-        """Write the given message to the log."""
-        buffer = logView.get_buffer()
-        appendTextBuffer(buffer, msg, isFault = isFault)
-        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 isWizardActive(self):
-        """Determine if the flight wizard is active."""
-        return self._notebook.get_current_page()==0
-
-    def _editChecklist(self, menuItem):
-        """Callback for editing the checklists."""
-        self._checklistEditor.run()
-
-    def _editApproachCallouts(self, menuItem):
-        """Callback for editing the approach callouts."""
-        self._approachCalloutsEditor.run()
-
-    def _editPreferences(self, menuItem):
-        """Callback for editing the preferences."""
-        self._clearHotkeys()
-        self._preferences.run(self.config)
-        self._setupTimeSync()
-        self._listenHotkeys()
-
-    def _reportBug(self, menuItem):
-        """Callback for reporting a bug."""
-        self._bugReportDialog.run()
-
-    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()
-                while True:
-                    result = dialog.run()
-
-                    if result==RESPONSETYPE_OK:
-                        self.sendPIREP(pirep)
-                    elif result==1:
-                        self._pirepViewer.setPIREP(pirep)
-                        self._pirepViewer.show_all()
-                        self._pirepViewer.run()
-                        self._pirepViewer.hide()
-                    else:
-                        break
-
-                dialog.hide()
-
-    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("file_filter_pireps"))
-            filter.add_pattern("*.pirep")
-            dialog.add_filter(filter)
-
-            filter = gtk.FileFilter()
-            filter.set_name(xstr("file_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("viewPIREP"), 1)
-        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)
-
-    def sendBugReport(self, summary, description, email, callback = None):
-        """Send the bug report with the given data."""
-        description += "\n\n" + ("=" * 40)
-        description += "\n\nThe contents of the log:\n\n"
-
-        for (timestampString, text) in self._logger.lines:
-            description += unicode(formatFlightLogLine(timestampString, text))
-
-        description += "\n\n" + ("=" * 40)
-        description += "\n\nThe contents of the debug log:\n\n"
-
-        buffer = self._debugLogView.get_buffer()
-        description += buffer.get_text(buffer.get_start_iter(),
-                                       buffer.get_end_iter(), True)
-
-        self.beginBusy(xstr("sendBugReport_busy"))
-        self._sendBugReportCallback = callback
-        self.webHandler.sendBugReport(self._bugReportSentCallback,
-                                      summary, description, email)
-
-    def _cefInitialized(self):
-        """Called when CEF has been initialized."""
-        self._acars.start()
-        cef.initializeSimBrief()
-
-    def _bugReportSentCallback(self, returned, result):
-        """Callback function for the bug report sending result."""
-        gobject.idle_add(self._handleBugReportSent, returned, result)
-
-    def _handleBugReportSent(self, returned, result):
-        """Callback for the bug report sending result."""
-        self.endBusy()
-        secondaryMarkup = None
-        type = MESSAGETYPE_ERROR
-        if returned:
-            if result.success:
-                type = MESSAGETYPE_INFO
-                messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
-                secondaryMarkup = xstr("sendBugReport_success_sec")
-            else:
-                messageFormat = xstr("sendBugReport_error")
-                secondaryMarkup = xstr("sendBugReport_siteerror_sec")
-        else:
-            messageFormat = xstr("sendBugReport_error")
-            secondaryMarkup = xstr("sendBugReport_error_sec")
-
-        dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
-                                   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._sendBugReportCallback
-        self._sendBugReportCallback = None
-        if callback is not None:
-            callback(returned, result)
-
-    def _listenHotkeys(self):
-        """Setup the hotkeys based on the configuration."""
-        if self._hotkeySetID is None and self._simulator is not None:
-            self._pilotHotkeyIndex = None
-            self._checklistHotkeyIndex = None
-
-            hotkeys = []
-
-            config = self.config
-            if config.enableSounds and config.pilotControlsSounds:
-                self._pilotHotkeyIndex = len(hotkeys)
-                hotkeys.append(config.pilotHotkey)
-
-            if config.enableChecklists:
-                self._checklistHotkeyIndex = len(hotkeys)
-                hotkeys.append(config.checklistHotkey)
-
-            if hotkeys:
-                self._hotkeySetID = \
-                    self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
-
-    def _clearHotkeys(self):
-        """Clear the hotkeys."""
-        if self._hotkeySetID is not None:
-            self._hotkeySetID=None
-            self._simulator.clearHotkeys()
-
-    def _handleHotkeys(self, id, hotkeys):
-        """Handle the hotkeys."""
-        if id==self._hotkeySetID:
-            for index in hotkeys:
-                if index==self._pilotHotkeyIndex:
-                    print "gui.GUI._handleHotkeys: pilot hotkey pressed"
-                    self._flight.pilotHotkeyPressed()
-                elif index==self._checklistHotkeyIndex:
-                    print "gui.GUI._handleHotkeys: checklist hotkey pressed"
-                    self._flight.checklistHotkeyPressed()
-                else:
-                    print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
-
-    def _showManual(self, menuitem):
-        """Show the user's manual."""
-        webbrowser.open(url ="file://" +
-                        os.path.join(self._programDirectory, "doc", "manual",
-                                     getLanguage(), "index.html"),
-                        new = 1)
-
-    def _showAbout(self, menuitem):
-        """Show the about dialog."""
-        dialog = self._getAboutDialog()
-        dialog.show_all()
-        dialog.run()
-        dialog.hide()
-
-    def _getAboutDialog(self):
-        """Get the about dialog.
-
-        If it does not exist yet, it will be created."""
-        if self._aboutDialog is None:
-            dialog = gtk.AboutDialog()
-            dialog.set_transient_for(self._mainWindow)
-            dialog.set_modal(True)
-
-            logoPath = os.path.join(self._programDirectory, "logo.png")
-            logo = pixbuf_new_from_file(logoPath)
-            dialog.set_logo(logo)
-
-            dialog.set_program_name(PROGRAM_NAME)
-            dialog.set_version(const.VERSION)
-            dialog.set_copyright("(c) 2012 by István Váradi")
-            dialog.set_website("http://mlx.varadiistvan.hu")
-            dialog.set_website_label(xstr("about_website"))
-
-            isHungarian = getLanguage()=="hu"
-            authors = []
-            for (familyName, firstName, role) in GUI._authors:
-                author = "%s %s" % \
-                         (familyName if isHungarian else firstName,
-                          firstName if isHungarian else familyName)
-                role = xstr("about_role_" + role)
-                authors.append(author + " (" + role + ")")
-            dialog.set_authors(authors)
-
-            dialog.set_license(xstr("about_license"))
-
-            if not pygobject:
-                gtk.about_dialog_set_url_hook(self._showAboutURL, None)
-
-            self._aboutDialog = dialog
-
-        return self._aboutDialog
-
-    def _showAboutURL(self, dialog, link, user_data):
-        """Show the about URL."""
-        webbrowser.open(url = link, new = 1)
-
-    def _setTakeoffAntiIceOn(self, value):
-        """Set the anti-ice on indicator."""
-        self._wizard.takeoffAntiIceOn = value
-
-    def _setLandingAntiIceOn(self, value):
-        """Set the anti-ice on indicator."""
-        self._wizard.landingAntiIceOn = value
Index: src/mlx/gui/info.py
===================================================================
--- src/mlx/gui/info.py	(revision 620)
+++ src/mlx/gui/info.py	(revision 620)
@@ -0,0 +1,180 @@
+
+from common import *
+
+from mlx.gui.delaycodes import DelayCodeTable
+from mlx.gui.faultexplain import FaultExplainWidget
+
+from mlx.i18n import xstr
+import mlx.const as const
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.info
+#
+# The flight info tab.
+#
+# This module implements to \ref FlightInfo class, which is the widget for the
+# extra information related to the flight. It contains a text area for the
+# comments, the fault list widget, and the frame for the delay codes at the
+# bottom in the centre.
+
+#------------------------------------------------------------------------------
+
+class FlightInfo(gtk.VBox):
+    """The flight info tab."""
+    @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()
+        scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scroller.set_shadow_type(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()
+        commentsBox.set_homogeneous(True)
+
+        (frame, self._comments) = FlightInfo._createCommentArea(xstr("info_comments"))
+        commentsBox.pack_start(frame, True, True, 8)
+        self._comments.get_buffer().connect("changed", self._commentsChanged)
+
+        self._faultExplainWidget = FaultExplainWidget(gui)
+        self._faultExplainWidget.connect("explanations-changed",
+                                         self._faultExplanationsChanged)
+        commentsBox.pack_start(self._faultExplainWidget, 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 = 1.0, yscale = 1.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 8, padding_right = 8)
+
+        self._delayCodeTable = table = DelayCodeTable(self)
+        self._delayWindow = scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(table)
+        scrolledWindow.set_size_request(-1, 185)
+        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(SHADOW_IN)
+
+        alignment.add(scrolledWindow)
+        frame.add(alignment)
+
+        self._delayAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
+                                             xscale = 1.0, yscale = 1.0)
+        self._delayAlignment.add(frame)
+        self._delayAlignment.set_padding(padding_top = 0, padding_bottom = 0,
+                                         padding_left = 8, padding_right = 8)
+
+        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 hasComments(self):
+        """Get whether there is any text in comments field."""
+        return self._comments.get_buffer().get_char_count()>0
+
+    @property
+    def faultsAndExplanations(self):
+        """Get the faults and explanations as HTML."""
+        return self._faultExplainWidget.html
+
+    @property
+    def delayCodes(self):
+        """Get the list of delay codes checked by the user."""
+        return self._delayCodeTable.delayCodes
+
+    @property
+    def hasDelayCode(self):
+        """Determine if there is at least one delay code selected."""
+        return self._delayCodeTable.hasDelayCode
+
+    @property
+    def faultsFullyExplained(self):
+        """Determine if all the faults have been explained by the pilot."""
+        return self._faultExplainWidget.fullyExplained
+
+    def addFault(self, id, faultText):
+        """Add a fault to the list of faults."""
+        self._faultExplainWidget.addFault(id, faultText)
+
+    def updateFault(self, id, faultText):
+        """Update a fault to the list of faults."""
+        self._faultExplainWidget.updateFault(id, faultText)
+
+    def clearFault(self, id):
+        """Clear a fault to the list of faults."""
+        self._faultExplainWidget.clearFault(id)
+
+    def enable(self, aircraftType):
+        """Enable the flight info tab."""
+        self._comments.set_sensitive(True)
+        self._faultExplainWidget.set_sensitive(True)
+        self._delayCodeTable.setType(aircraftType)
+        self._delayWindow.set_sensitive(True)
+        self._delayCodeTable.setStyle()
+
+    def disable(self):
+        """Enable the flight info tab."""
+        self._comments.set_sensitive(False)
+        self._faultExplainWidget.set_sensitive(False)
+        self._delayWindow.set_sensitive(False)
+        self._delayCodeTable.setStyle()
+
+    def reset(self):
+        """Reset the flight info tab."""
+        self._comments.get_buffer().set_text("")
+        self._faultExplainWidget.reset()
+        self._delayCodeTable.reset()
+
+    def delayCodesChanged(self):
+        """Callewd when the delay codes have changed."""
+        self._gui.delayCodesChanged()
+
+    def _commentsChanged(self, textbuffer):
+        """Called when the comments have changed."""
+        self._gui.commentsChanged()
+
+    def _faultExplanationsChanged(self, faultExplainWidget, fullyExplained):
+        """Called when the status of the fault explanations has changed."""
+        self._gui.faultExplanationsChanged()
Index: src/mlx/gui/info.py
===================================================================
--- src/mlx/gui/info.py	(revision 620)
+++ 	(revision )
@@ -1,180 +1,0 @@
-
-from common import *
-
-from mlx.gui.delaycodes import DelayCodeTable
-from mlx.gui.faultexplain import FaultExplainWidget
-
-from mlx.i18n import xstr
-import mlx.const as const
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.info
-#
-# The flight info tab.
-#
-# This module implements to \ref FlightInfo class, which is the widget for the
-# extra information related to the flight. It contains a text area for the
-# comments, the fault list widget, and the frame for the delay codes at the
-# bottom in the centre.
-
-#------------------------------------------------------------------------------
-
-class FlightInfo(gtk.VBox):
-    """The flight info tab."""
-    @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()
-        scroller.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scroller.set_shadow_type(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()
-        commentsBox.set_homogeneous(True)
-
-        (frame, self._comments) = FlightInfo._createCommentArea(xstr("info_comments"))
-        commentsBox.pack_start(frame, True, True, 8)
-        self._comments.get_buffer().connect("changed", self._commentsChanged)
-
-        self._faultExplainWidget = FaultExplainWidget(gui)
-        self._faultExplainWidget.connect("explanations-changed",
-                                         self._faultExplanationsChanged)
-        commentsBox.pack_start(self._faultExplainWidget, 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 = 1.0, yscale = 1.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 8, padding_right = 8)
-
-        self._delayCodeTable = table = DelayCodeTable(self)
-        self._delayWindow = scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(table)
-        scrolledWindow.set_size_request(-1, 185)
-        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(SHADOW_IN)
-
-        alignment.add(scrolledWindow)
-        frame.add(alignment)
-
-        self._delayAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
-                                             xscale = 1.0, yscale = 1.0)
-        self._delayAlignment.add(frame)
-        self._delayAlignment.set_padding(padding_top = 0, padding_bottom = 0,
-                                         padding_left = 8, padding_right = 8)
-
-        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 hasComments(self):
-        """Get whether there is any text in comments field."""
-        return self._comments.get_buffer().get_char_count()>0
-
-    @property
-    def faultsAndExplanations(self):
-        """Get the faults and explanations as HTML."""
-        return self._faultExplainWidget.html
-
-    @property
-    def delayCodes(self):
-        """Get the list of delay codes checked by the user."""
-        return self._delayCodeTable.delayCodes
-
-    @property
-    def hasDelayCode(self):
-        """Determine if there is at least one delay code selected."""
-        return self._delayCodeTable.hasDelayCode
-
-    @property
-    def faultsFullyExplained(self):
-        """Determine if all the faults have been explained by the pilot."""
-        return self._faultExplainWidget.fullyExplained
-
-    def addFault(self, id, faultText):
-        """Add a fault to the list of faults."""
-        self._faultExplainWidget.addFault(id, faultText)
-
-    def updateFault(self, id, faultText):
-        """Update a fault to the list of faults."""
-        self._faultExplainWidget.updateFault(id, faultText)
-
-    def clearFault(self, id):
-        """Clear a fault to the list of faults."""
-        self._faultExplainWidget.clearFault(id)
-
-    def enable(self, aircraftType):
-        """Enable the flight info tab."""
-        self._comments.set_sensitive(True)
-        self._faultExplainWidget.set_sensitive(True)
-        self._delayCodeTable.setType(aircraftType)
-        self._delayWindow.set_sensitive(True)
-        self._delayCodeTable.setStyle()
-
-    def disable(self):
-        """Enable the flight info tab."""
-        self._comments.set_sensitive(False)
-        self._faultExplainWidget.set_sensitive(False)
-        self._delayWindow.set_sensitive(False)
-        self._delayCodeTable.setStyle()
-
-    def reset(self):
-        """Reset the flight info tab."""
-        self._comments.get_buffer().set_text("")
-        self._faultExplainWidget.reset()
-        self._delayCodeTable.reset()
-
-    def delayCodesChanged(self):
-        """Callewd when the delay codes have changed."""
-        self._gui.delayCodesChanged()
-
-    def _commentsChanged(self, textbuffer):
-        """Called when the comments have changed."""
-        self._gui.commentsChanged()
-
-    def _faultExplanationsChanged(self, faultExplainWidget, fullyExplained):
-        """Called when the status of the fault explanations has changed."""
-        self._gui.faultExplanationsChanged()
Index: src/mlx/gui/mava_simbrief.py
===================================================================
--- src/mlx/gui/mava_simbrief.py	(revision 685)
+++ src/mlx/gui/mava_simbrief.py	(revision 685)
@@ -0,0 +1,331 @@
+# needed for os specific separators
+import os
+# selenium is needed for browser manipulation
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+from selenium.common.exceptions import NoSuchWindowException
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait, Select
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.common.keys import Keys
+# urllib is needed to obtain the xml
+import urllib2
+# xmlparser
+from lxml import etree
+from StringIO import StringIO
+import lxml.html
+import time
+
+
+class MavaSimbriefIntegrator():
+    """Implements the integration with the excellent Simbrief pilot briefing
+    system for MALEV Virtual."""
+
+    def __init__(self,
+                 plan,
+                 driver=None,
+                 simbrief_query_settings=None,
+                 mava_simbrief_url=None,
+                 xml_link_fix_part=None):
+        """Init the integrator with settings that are typical of our use.
+        @param: plan - flightplan dictionary
+        @param: webdriver - a selenium webdriver
+        @param: simbrief_query_settings - a dictionary of query settings
+        @param: mava_simbrief_url - url to the form that is sent to simbrief
+        on the mava server
+        @param: xml_link_fix_part = url to the simbrief website under which the
+        xml is to be found"""
+        self.plan = plan
+        if simbrief_query_settings is None:
+            self.simbrief_query_settings = {
+                'navlog': True,
+                'etops': True,
+                'stepclimbs': True,
+                'tlr': True,
+                'notams': True,
+                'firnot': True,
+                'maps': 'Simple',
+            }
+        else:
+            self.simbrief_query_settings = simbrief_query_settings
+
+        if driver is None:
+            self.driver = webdriver.Firefox()
+        else:
+            self.driver = driver
+
+        if mava_simbrief_url is None:
+            self.mava_simbrief_url = "http://flare.privatedns.org/" \
+                                     "mava_simbrief/simbrief_form.html"
+        else:
+            self.mava_simbrief_url = mava_simbrief_url
+
+        if xml_link_fix_part is None:
+            self.xml_link_fix_part = "http://www.simbrief.com/ofp/" \
+                                     "flightplans/xml/"
+        else:
+            self.xml_link_fix_part = xml_link_fix_part
+
+    def fill_form(self,
+                  plan,
+                  simbrief_query_settings):
+        """Fills the form of the webpage using the paramteres that the class
+        has been initialized with.
+        @param: driver - a selenium webdriver
+        @param: plan - dictionary containing plan details
+        @param: simbrief_query_settings - dictionary containing plan settings"""
+        for plan_input_field in plan.iterkeys():
+            self.driver.find_element_by_name(plan_input_field).send_keys(
+                plan[plan_input_field])
+        for option_checkbox in simbrief_query_settings.iterkeys():
+            if (isinstance(simbrief_query_settings[option_checkbox], bool) and
+                    simbrief_query_settings[option_checkbox]):
+                # if setting is a boolean type and true
+                self.driver.find_element_by_name(option_checkbox).click()
+            elif isinstance(simbrief_query_settings[option_checkbox], str):
+                # if setting is a select
+                Select(self.driver.find_element_by_name(option_checkbox)).\
+                    select_by_visible_text(simbrief_query_settings[
+                    option_checkbox])
+
+    def get_xml_link(self,
+                     get_credentials, update_progress,
+                     local_xml_debug=False,
+                     local_html_debug=False):
+        """Obtains the link of the xml to be processed.
+        @param get_credentials - a function, which should return a pair of the
+        user name and password to log in to SimBrief. It gets an integer which
+        is the number of times it has been called so far.
+        @param local_xml_debug - if True then not the real location will be
+        checked but the current working dir will be searched for 'xml.xml'
+        @param local_html_debug - if True then not real mava_simbrief_url will
+        be used but the current working dir will be searched for
+        'mava_simbrief.html'
+        @returns: the string of the xml link if it could be obtained and None
+        otherwise"""
+        # Finding xml_link
+        is_briefing_available = False
+        if local_xml_debug:
+            # set link for locally debugging an existing xml file
+            xml_link = ("file://"
+                        + os.getcwd()
+                        + os.sep
+                        + "xml.xml")
+            is_briefing_available = True
+        else:
+            update_progress("Looking for a browser...", False)
+            # There must be window whose title is 'SimBrief' so that we
+            # could find our one among several
+            if self._find_window_by_title("SimBrief") is None:
+                print "No SimBrief window was found!"
+                return None
+
+            # normal operation with a real xml file
+            update_progress("Retrieving form...", False)
+            if local_html_debug:
+                self.driver.get(
+                    "file://" + os.getcwd() + os.sep + "simbrief_form.html")
+                is_briefing_available = True
+            else:
+                self.driver.get(self.mava_simbrief_url)
+
+            main_handle = self._find_window_by_title("Malev Virtual Simbrief Integration System")
+            if main_handle is None:
+                print "No SimBrief Integration window was found!"
+                return None
+
+            # Make a copy of the window handles before submitting the form,
+            # so that we could find the popup
+            handles = self.driver.window_handles[:]
+
+            # Entering form data
+            update_progress("Filling form...", False)
+            self.driver.switch_to_window(main_handle)
+            self.fill_form(self.plan,
+                           self.simbrief_query_settings)
+
+            # Loading page
+            button = self.driver.find_element_by_name("submitform")
+            button.send_keys(Keys.RETURN)
+
+            update_progress("Waiting for login...", False)
+            popup_handle = self._find_popup(handles)
+            if popup_handle is None:
+                print "No popup window was found!"
+                return None
+
+            login_count = 0
+            end_time = time.time() + 120.0
+            while not is_briefing_available and end_time > time.time():
+                try:
+                    self.driver.switch_to.window(popup_handle)
+
+                    userElement = self.driver.find_element_by_name("user")
+
+                    if userElement is not None:
+                        update_progress("Logging in...", False)
+                        (userName, password) = get_credentials(login_count)
+                        userElement.send_keys(userName)
+                        self.driver.find_element_by_name("pass").send_keys(password)
+                        self.driver.find_element_by_name("staylogged").click()
+
+                        self.driver.find_element(By.XPATH,
+                                                "//input[@value='Login']").send_keys(Keys.RETURN)
+                        login_count += 1
+                except NoSuchElementException:
+                    pass
+                except NoSuchWindowException:
+                    pass
+
+                update_progress("Waiting for the result...", False)
+                self.driver.switch_to.window(main_handle)
+                try:
+                    if self.driver.find_element_by_name("hidden_is_briefing_available") is not None:
+                        is_briefing_available = True
+                        xml_link_element = self.driver.find_element_by_name(
+                            'hidden_link')
+                        xml_link_generated_part = xml_link_element.get_attribute(
+                            'value')
+                        xml_link = self.xml_link_fix_part + xml_link_generated_part + '.xml'
+                        print(xml_link)
+                except NoSuchElementException:
+                    pass
+
+            self.driver.quit()
+
+        if is_briefing_available:
+            return xml_link
+        else:
+            return None
+
+    def get_results(self,
+                    xml_link,
+                    html_file_path = None):
+        """Parses the xml for information.
+        @param xml_link - a path to the xml file
+        @return a dictionary of the found information"""
+        # Setup variables
+        ## Holds analysis data not used
+        available_info = {}
+        ## Holds analysis data to be used
+        flight_info = {}
+        ## Holds notams
+        notams_list = []
+        ## Notam counter
+        i = 0
+        # Obtaining the xml
+        response = urllib2.urlopen(xml_link)
+        xml_content = response.read()
+        # Processing xml
+        tree = etree.parse(StringIO(xml_content))
+        context = etree.iterparse(StringIO(xml_content))
+        for action, element in context:
+            # Processing tags that occur multiple times
+            ## NOTAMS
+            if element.tag == 'notamdrec':
+                notams_element_list = list(element)
+                notam_dict = {}
+                for notam in notams_element_list:
+                    notam_dict[notam.tag] = notam.text
+                notams_list.append(notam_dict)
+                i += 1
+            ## WEATHER
+            elif element.tag == 'weather':
+                weather_element_list = list(element)
+                for weather in weather_element_list:
+                    flight_info[weather.tag] = weather.text
+            else:
+                available_info[element.tag] = element.text
+        # Processing plan_html
+        ## Obtaining chart links
+        image_links = []
+        for image_link_a_element in lxml.html.find_class(
+                available_info['plan_html'], 'ofpmaplink'):
+            for image_link_tuple in image_link_a_element.iterlinks():
+                if image_link_tuple[1] == 'src':
+                    image_links.append(image_link_tuple[2])
+        flight_info['image_links'] = image_links
+        print(sorted(available_info.keys()))
+        if html_file_path is None:
+            html_file_path = 'simbrief_plan.html'
+        with open(html_file_path, 'w') as f:
+            f.write(available_info['plan_html'])
+        return flight_info
+
+    def _find_window_by_title(self, title, timeout = 10.0):
+        """Find the window with the given title.
+
+        Switch to that window and return its handle."""
+        def predicate(handle):
+            self.driver.switch_to.window(handle)
+            return self.driver.title == title
+
+        return self._find_window(predicate, timeout = timeout)
+
+    def _find_popup(self, handles, timeout = 10.0):
+        """Find a popup, i.e. a new window being created.
+
+        handles is a list of window handles that existed before the popup was
+        expected to be created. If a new handle is found, that is assumed to be
+        the popup window."""
+        return self._find_window(lambda handle: handle not in handles,
+                                 timeout = timeout)
+
+    def _find_window(self, predicate, timeout = 10.0):
+        """Find a window that fulfills the given predicate."""
+        window_handle = None
+        end_time = time.time() + timeout
+        while window_handle is None and end_time > time.time():
+            for handle in self.driver.window_handles:
+                if predicate(handle):
+                    window_handle = handle
+                    break
+
+        return window_handle
+
+if __name__ == "__main__":
+    mava_simbrief_url = "http://flare.privatedns.org/" \
+                        "mava_simbrief/simbrief_form.html"
+    xml_link_fix_part = "http://www.simbrief.com/ofp/" \
+                        "flightplans/xml/"
+    plan = {
+        'airline': 'MAH',
+        'fltnum': '764',
+        'type': 'B738',
+        'orig': 'LHBP',
+        'dest': 'LSZH',
+        'date': '25FEB15',
+        'deph': '20',
+        'depm': '00',
+        'route': 'GILEP DCT ARSIN UL851 SITNI UL856 NEGRA',
+        'steh': '22',
+        'stem': '05',
+        'reg': 'HA-LOC',
+        'fin': 'LOC',
+        'selcal': 'XXXX',
+        'pax': '100',
+        'altn': 'LSGG',
+        'fl': '36000',
+        'cpt': 'BALINT SZEBENYI',
+        'pid': 'P008',
+        'fuelfactor': 'P000',
+        'manualzfw': '42.7',
+        'addedfuel': '2.5',
+        'contpct': '0.05',
+        'resvrule': '45',
+        'taxiout': '10',
+        'taxiin': '4',
+        'cargo': '5.0',
+        'origrwy': '31L',
+        'destrwy': '34',
+        'climb': '250/300/78',
+        'descent': '80/280/250',
+        'cruise': 'LRC',
+        'civalue': 'AUTO',
+    }
+    integrator = MavaSimbriefIntegrator(plan=plan)
+    link = integrator.get_xml_link(local_xml_debug=False,
+                                   local_html_debug=False)
+    flight_info = integrator.get_results(link)
+    print(flight_info)
+
Index: src/mlx/gui/mava_simbrief.py
===================================================================
--- src/mlx/gui/mava_simbrief.py	(revision 685)
+++ 	(revision )
@@ -1,331 +1,0 @@
-# needed for os specific separators
-import os
-# selenium is needed for browser manipulation
-from selenium import webdriver
-from selenium.common.exceptions import NoSuchElementException
-from selenium.common.exceptions import NoSuchWindowException
-from selenium.webdriver.common.by import By
-from selenium.webdriver.support.ui import WebDriverWait, Select
-from selenium.webdriver.support import expected_conditions as EC
-from selenium.webdriver.common.keys import Keys
-# urllib is needed to obtain the xml
-import urllib2
-# xmlparser
-from lxml import etree
-from StringIO import StringIO
-import lxml.html
-import time
-
-
-class MavaSimbriefIntegrator():
-    """Implements the integration with the excellent Simbrief pilot briefing
-    system for MALEV Virtual."""
-
-    def __init__(self,
-                 plan,
-                 driver=None,
-                 simbrief_query_settings=None,
-                 mava_simbrief_url=None,
-                 xml_link_fix_part=None):
-        """Init the integrator with settings that are typical of our use.
-        @param: plan - flightplan dictionary
-        @param: webdriver - a selenium webdriver
-        @param: simbrief_query_settings - a dictionary of query settings
-        @param: mava_simbrief_url - url to the form that is sent to simbrief
-        on the mava server
-        @param: xml_link_fix_part = url to the simbrief website under which the
-        xml is to be found"""
-        self.plan = plan
-        if simbrief_query_settings is None:
-            self.simbrief_query_settings = {
-                'navlog': True,
-                'etops': True,
-                'stepclimbs': True,
-                'tlr': True,
-                'notams': True,
-                'firnot': True,
-                'maps': 'Simple',
-            }
-        else:
-            self.simbrief_query_settings = simbrief_query_settings
-
-        if driver is None:
-            self.driver = webdriver.Firefox()
-        else:
-            self.driver = driver
-
-        if mava_simbrief_url is None:
-            self.mava_simbrief_url = "http://flare.privatedns.org/" \
-                                     "mava_simbrief/simbrief_form.html"
-        else:
-            self.mava_simbrief_url = mava_simbrief_url
-
-        if xml_link_fix_part is None:
-            self.xml_link_fix_part = "http://www.simbrief.com/ofp/" \
-                                     "flightplans/xml/"
-        else:
-            self.xml_link_fix_part = xml_link_fix_part
-
-    def fill_form(self,
-                  plan,
-                  simbrief_query_settings):
-        """Fills the form of the webpage using the paramteres that the class
-        has been initialized with.
-        @param: driver - a selenium webdriver
-        @param: plan - dictionary containing plan details
-        @param: simbrief_query_settings - dictionary containing plan settings"""
-        for plan_input_field in plan.iterkeys():
-            self.driver.find_element_by_name(plan_input_field).send_keys(
-                plan[plan_input_field])
-        for option_checkbox in simbrief_query_settings.iterkeys():
-            if (isinstance(simbrief_query_settings[option_checkbox], bool) and
-                    simbrief_query_settings[option_checkbox]):
-                # if setting is a boolean type and true
-                self.driver.find_element_by_name(option_checkbox).click()
-            elif isinstance(simbrief_query_settings[option_checkbox], str):
-                # if setting is a select
-                Select(self.driver.find_element_by_name(option_checkbox)).\
-                    select_by_visible_text(simbrief_query_settings[
-                    option_checkbox])
-
-    def get_xml_link(self,
-                     get_credentials, update_progress,
-                     local_xml_debug=False,
-                     local_html_debug=False):
-        """Obtains the link of the xml to be processed.
-        @param get_credentials - a function, which should return a pair of the
-        user name and password to log in to SimBrief. It gets an integer which
-        is the number of times it has been called so far.
-        @param local_xml_debug - if True then not the real location will be
-        checked but the current working dir will be searched for 'xml.xml'
-        @param local_html_debug - if True then not real mava_simbrief_url will
-        be used but the current working dir will be searched for
-        'mava_simbrief.html'
-        @returns: the string of the xml link if it could be obtained and None
-        otherwise"""
-        # Finding xml_link
-        is_briefing_available = False
-        if local_xml_debug:
-            # set link for locally debugging an existing xml file
-            xml_link = ("file://"
-                        + os.getcwd()
-                        + os.sep
-                        + "xml.xml")
-            is_briefing_available = True
-        else:
-            update_progress("Looking for a browser...", False)
-            # There must be window whose title is 'SimBrief' so that we
-            # could find our one among several
-            if self._find_window_by_title("SimBrief") is None:
-                print "No SimBrief window was found!"
-                return None
-
-            # normal operation with a real xml file
-            update_progress("Retrieving form...", False)
-            if local_html_debug:
-                self.driver.get(
-                    "file://" + os.getcwd() + os.sep + "simbrief_form.html")
-                is_briefing_available = True
-            else:
-                self.driver.get(self.mava_simbrief_url)
-
-            main_handle = self._find_window_by_title("Malev Virtual Simbrief Integration System")
-            if main_handle is None:
-                print "No SimBrief Integration window was found!"
-                return None
-
-            # Make a copy of the window handles before submitting the form,
-            # so that we could find the popup
-            handles = self.driver.window_handles[:]
-
-            # Entering form data
-            update_progress("Filling form...", False)
-            self.driver.switch_to_window(main_handle)
-            self.fill_form(self.plan,
-                           self.simbrief_query_settings)
-
-            # Loading page
-            button = self.driver.find_element_by_name("submitform")
-            button.send_keys(Keys.RETURN)
-
-            update_progress("Waiting for login...", False)
-            popup_handle = self._find_popup(handles)
-            if popup_handle is None:
-                print "No popup window was found!"
-                return None
-
-            login_count = 0
-            end_time = time.time() + 120.0
-            while not is_briefing_available and end_time > time.time():
-                try:
-                    self.driver.switch_to.window(popup_handle)
-
-                    userElement = self.driver.find_element_by_name("user")
-
-                    if userElement is not None:
-                        update_progress("Logging in...", False)
-                        (userName, password) = get_credentials(login_count)
-                        userElement.send_keys(userName)
-                        self.driver.find_element_by_name("pass").send_keys(password)
-                        self.driver.find_element_by_name("staylogged").click()
-
-                        self.driver.find_element(By.XPATH,
-                                                "//input[@value='Login']").send_keys(Keys.RETURN)
-                        login_count += 1
-                except NoSuchElementException:
-                    pass
-                except NoSuchWindowException:
-                    pass
-
-                update_progress("Waiting for the result...", False)
-                self.driver.switch_to.window(main_handle)
-                try:
-                    if self.driver.find_element_by_name("hidden_is_briefing_available") is not None:
-                        is_briefing_available = True
-                        xml_link_element = self.driver.find_element_by_name(
-                            'hidden_link')
-                        xml_link_generated_part = xml_link_element.get_attribute(
-                            'value')
-                        xml_link = self.xml_link_fix_part + xml_link_generated_part + '.xml'
-                        print(xml_link)
-                except NoSuchElementException:
-                    pass
-
-            self.driver.quit()
-
-        if is_briefing_available:
-            return xml_link
-        else:
-            return None
-
-    def get_results(self,
-                    xml_link,
-                    html_file_path = None):
-        """Parses the xml for information.
-        @param xml_link - a path to the xml file
-        @return a dictionary of the found information"""
-        # Setup variables
-        ## Holds analysis data not used
-        available_info = {}
-        ## Holds analysis data to be used
-        flight_info = {}
-        ## Holds notams
-        notams_list = []
-        ## Notam counter
-        i = 0
-        # Obtaining the xml
-        response = urllib2.urlopen(xml_link)
-        xml_content = response.read()
-        # Processing xml
-        tree = etree.parse(StringIO(xml_content))
-        context = etree.iterparse(StringIO(xml_content))
-        for action, element in context:
-            # Processing tags that occur multiple times
-            ## NOTAMS
-            if element.tag == 'notamdrec':
-                notams_element_list = list(element)
-                notam_dict = {}
-                for notam in notams_element_list:
-                    notam_dict[notam.tag] = notam.text
-                notams_list.append(notam_dict)
-                i += 1
-            ## WEATHER
-            elif element.tag == 'weather':
-                weather_element_list = list(element)
-                for weather in weather_element_list:
-                    flight_info[weather.tag] = weather.text
-            else:
-                available_info[element.tag] = element.text
-        # Processing plan_html
-        ## Obtaining chart links
-        image_links = []
-        for image_link_a_element in lxml.html.find_class(
-                available_info['plan_html'], 'ofpmaplink'):
-            for image_link_tuple in image_link_a_element.iterlinks():
-                if image_link_tuple[1] == 'src':
-                    image_links.append(image_link_tuple[2])
-        flight_info['image_links'] = image_links
-        print(sorted(available_info.keys()))
-        if html_file_path is None:
-            html_file_path = 'simbrief_plan.html'
-        with open(html_file_path, 'w') as f:
-            f.write(available_info['plan_html'])
-        return flight_info
-
-    def _find_window_by_title(self, title, timeout = 10.0):
-        """Find the window with the given title.
-
-        Switch to that window and return its handle."""
-        def predicate(handle):
-            self.driver.switch_to.window(handle)
-            return self.driver.title == title
-
-        return self._find_window(predicate, timeout = timeout)
-
-    def _find_popup(self, handles, timeout = 10.0):
-        """Find a popup, i.e. a new window being created.
-
-        handles is a list of window handles that existed before the popup was
-        expected to be created. If a new handle is found, that is assumed to be
-        the popup window."""
-        return self._find_window(lambda handle: handle not in handles,
-                                 timeout = timeout)
-
-    def _find_window(self, predicate, timeout = 10.0):
-        """Find a window that fulfills the given predicate."""
-        window_handle = None
-        end_time = time.time() + timeout
-        while window_handle is None and end_time > time.time():
-            for handle in self.driver.window_handles:
-                if predicate(handle):
-                    window_handle = handle
-                    break
-
-        return window_handle
-
-if __name__ == "__main__":
-    mava_simbrief_url = "http://flare.privatedns.org/" \
-                        "mava_simbrief/simbrief_form.html"
-    xml_link_fix_part = "http://www.simbrief.com/ofp/" \
-                        "flightplans/xml/"
-    plan = {
-        'airline': 'MAH',
-        'fltnum': '764',
-        'type': 'B738',
-        'orig': 'LHBP',
-        'dest': 'LSZH',
-        'date': '25FEB15',
-        'deph': '20',
-        'depm': '00',
-        'route': 'GILEP DCT ARSIN UL851 SITNI UL856 NEGRA',
-        'steh': '22',
-        'stem': '05',
-        'reg': 'HA-LOC',
-        'fin': 'LOC',
-        'selcal': 'XXXX',
-        'pax': '100',
-        'altn': 'LSGG',
-        'fl': '36000',
-        'cpt': 'BALINT SZEBENYI',
-        'pid': 'P008',
-        'fuelfactor': 'P000',
-        'manualzfw': '42.7',
-        'addedfuel': '2.5',
-        'contpct': '0.05',
-        'resvrule': '45',
-        'taxiout': '10',
-        'taxiin': '4',
-        'cargo': '5.0',
-        'origrwy': '31L',
-        'destrwy': '34',
-        'climb': '250/300/78',
-        'descent': '80/280/250',
-        'cruise': 'LRC',
-        'civalue': 'AUTO',
-    }
-    integrator = MavaSimbriefIntegrator(plan=plan)
-    link = integrator.get_xml_link(local_xml_debug=False,
-                                   local_html_debug=False)
-    flight_info = integrator.get_results(link)
-    print(flight_info)
-
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 680)
+++ src/mlx/gui/monitor.py	(revision 680)
@@ -0,0 +1,439 @@
+
+from mlx.gui.common import *
+
+import mlx.const as const
+import mlx.util as util
+
+import time
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.monitor
+#
+# The monitoring window
+#
+# The \ref MonitorWindow class is a window containing the data received from
+# the simulator by the logger.
+
+#------------------------------------------------------------------------------
+
+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_resizable(False)
+        self.set_title(WINDOW_TITLE_BASE + " - 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 = 9, columns = 14)
+        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, 11, 0, 1)
+
+        self._antiIceOn = gtk.Label("ANTI-ICE")
+        table.attach(self._antiIceOn, 11, 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:", 13)
+        table.attach(label, 10, 11, 1, 2)
+        table.attach(self._vs, 11, 12, 1, 2)
+
+        (label, self._ias) = self._createLabeledEntry("IAS:", 11)
+        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, 2, 5, 6)
+
+        self._strobeLightsOn = gtk.Label("STROBE")
+        table.attach(self._strobeLightsOn, 2, 3, 5, 6)
+
+        self._landingLightsOn = gtk.Label("LANDING")
+        table.attach(self._landingLightsOn, 3, 4, 5, 6)
+
+        self._pitotHeatOn = gtk.Label("PITOT HEAT")
+        table.attach(self._pitotHeatOn, 4, 5, 5, 6)
+
+        self._parking = gtk.Label("PARKING")
+        table.attach(self._parking, 5, 6, 5, 6)
+
+        self._gearControlDown = gtk.Label("GEAR LEVER DOWN")
+        table.attach(self._gearControlDown, 6, 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)
+
+        (label, self._elevatorTrim) = self._createLabeledEntry("Trim:", 5)
+        table.attach(label, 10, 11, 6, 7)
+        table.attach(self._elevatorTrim, 11, 12, 6, 7)
+
+        self._apMaster = gtk.Label("AP MASTER")
+        table.attach(self._apMaster, 0, 1, 7, 8)
+
+        self._apHeadingHold = gtk.Label("AP HDG HOLD")
+        table.attach(self._apHeadingHold, 1, 2, 7, 8)
+
+        (label, self._apHeading) = self._createLabeledEntry("AP HDG:", 5)
+        table.attach(label, 2, 3, 7, 8)
+        table.attach(self._apHeading, 3, 4, 7, 8)
+
+        self._xpdrC = gtk.Label("XPDR C")
+        table.attach(self._xpdrC, 4, 5, 7, 8)
+
+        self._apAltitudeHold = gtk.Label("AP ALT HOLD")
+        table.attach(self._apAltitudeHold, 5, 6, 7, 8)
+
+        (label, self._apAltitude) = self._createLabeledEntry("AP ALT:", 10)
+        table.attach(label, 6, 7, 7, 8)
+        table.attach(self._apAltitude, 7, 8, 7, 8)
+
+        (label, self._adf1) = self._createLabeledEntry("ADF1:", 5)
+        table.attach(label, 8, 9, 7, 8)
+        table.attach(self._adf1, 9, 10, 7, 8)
+
+        (label, self._adf2) = self._createLabeledEntry("ADF2:", 5)
+        table.attach(label, 10, 11, 7, 8)
+        table.attach(self._adf2, 11, 12, 7, 8)
+
+        (label, self._qnh) = self._createLabeledEntry("QNH:", 7)
+        table.attach(label, 0, 1, 8, 9)
+        table.attach(self._qnh, 1, 2, 8, 9)
+
+        (label, self._cog) = self._createLabeledEntry("CoG:", 7)
+        table.attach(label, 2, 3, 8, 9)
+        table.attach(self._cog, 3, 4, 8, 9)
+
+        (label, self._ils) = self._createLabeledEntry("ILS:", 7)
+        table.attach(label, 4, 5, 8, 9)
+        table.attach(self._ils, 5, 6, 8, 9)
+
+        (label, self._crs1) = self._createLabeledEntry("CRS1:", 7)
+        table.attach(label, 6, 7, 8, 9)
+        table.attach(self._crs1, 7, 8, 8, 9)
+
+        (label, self._crs2) = self._createLabeledEntry("CRS2:", 7)
+        table.attach(label, 8, 9, 8, 9)
+        table.attach(self._crs2, 9, 10, 8, 9)
+
+        (label, self._crsi) = self._createLabeledEntry("CRSI:", 7)
+        table.attach(label, 10, 11, 8, 9)
+        table.attach(self._crsi, 11, 12, 8, 9)
+
+        alignment.add(table)
+
+        self.add(alignment)
+
+        self._previousState = None
+
+        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._antiIceOn.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_text("LANDING")
+            self._landingLightsOn.set_sensitive(False)
+            self._pitotHeatOn.set_sensitive(False)
+            self._parking.set_sensitive(False)
+            self._gearControlDown.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("-")
+            self._elevatorTrim.set_text("-")
+            self._apMaster.set_sensitive(False)
+            self._apHeadingHold.set_sensitive(False)
+            self._apHeading.set_text("-")
+            self._xpdrC.set_sensitive(False)
+            self._apAltitudeHold.set_sensitive(False)
+            self._apAltitude.set_text("-")
+            self._adf1.set_text("-")
+            self._adf2.set_text("-")
+            self._qnh.set_text("-")
+            self._cog.set_text("-")
+            self._ils.set_text("-")
+            self._crs1.set_text("-")
+            self._crs2.set_text("-")
+            self._crsi.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._antiIceOn.set_sensitive(aircraftState.antiIceOn is True)
+            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 (%.0f)" % (aircraftState.vs,
+                                               aircraftState.smoothedVS))
+            self._ias.set_text("%.0f (%.0f)" % (aircraftState.ias,
+                                                aircraftState.smoothedIAS))
+            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("-" if aircraftState.nav1 is None else aircraftState.nav1)
+            self._nav2.set_text("-" if aircraftState.nav2 is None else aircraftState.nav2)
+            self._qnh.set_text("%.0f" % (aircraftState.qnh,))
+
+            fuelStr = ""
+            for (_tank, fuel) in aircraftState.fuel:
+                if fuelStr: fuelStr += ", "
+                fuelStr += "%.0f" % (fuel,)
+            self._fuel.set_text(fuelStr)
+
+            if aircraftState.n1 is not None:
+                n1Str = ""
+                for n1 in aircraftState.n1:
+                    if n1Str: n1Str += ", "
+                    n1Str += "%.0f" % (n1,)
+            elif aircraftState.rpm is not None:
+                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 is True)
+            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn is True)
+            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn is True)
+
+            if self._previousState is None or \
+               ((self._previousState.landingLightsOn is None)!=
+                (aircraftState.landingLightsOn is None)):
+                if aircraftState.landingLightsOn is None:
+                    self._landingLightsOn.set_markup('<span strikethrough="true">LANDING</span>')
+                else:
+                    self._landingLightsOn.set_text("LANDING")
+            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn is True)
+
+            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn is True)
+            self._parking.set_sensitive(aircraftState.parking)
+            self._gearControlDown.set_sensitive(aircraftState.gearControlDown)
+            self._gearsDown.set_sensitive(aircraftState.gearsDown)
+            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed is True)
+            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)))
+            self._elevatorTrim.set_text("%.2f" % (aircraftState.elevatorTrim,))
+            self._apMaster.set_sensitive(aircraftState.apMaster is True)
+            self._apHeadingHold.set_sensitive(aircraftState.apHeadingHold is True)
+            self._apHeading.set_text("-" if aircraftState.apHeading is None
+                                     else "%03.0f" % (aircraftState.apHeading,))
+            self._xpdrC.set_sensitive(aircraftState.xpdrC is True)
+            self._apAltitudeHold.set_sensitive(aircraftState.apAltitudeHold is True)
+            self._apAltitude.set_text("-" if aircraftState.apAltitude is None
+                                      else "%5.0f" % (aircraftState.apAltitude,))
+
+            self._adf1.set_text("-" if aircraftState.adf1 is None else aircraftState.adf1)
+            self._adf2.set_text("-" if aircraftState.adf2 is None else aircraftState.adf2)
+            self._cog.set_text("%.2f%%" % (aircraftState.cog*100.0,))
+
+            self._ils.set_text("-" if aircraftState.ils is None else aircraftState.ils)
+
+            self._crs1.set_text("-" if aircraftState.nav1_obs is None else str(aircraftState.nav1_obs))
+            self._crs2.set_text("-" if aircraftState.nav2_obs is None else str(aircraftState.nav2_obs))
+            self._crsi.set_text("-" if aircraftState.ils_obs is None else str(aircraftState.ils_obs))
+
+#------------------------------------------------------------------------------
Index: src/mlx/gui/monitor.py
===================================================================
--- src/mlx/gui/monitor.py	(revision 680)
+++ 	(revision )
@@ -1,439 +1,0 @@
-
-from mlx.gui.common import *
-
-import mlx.const as const
-import mlx.util as util
-
-import time
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.monitor
-#
-# The monitoring window
-#
-# The \ref MonitorWindow class is a window containing the data received from
-# the simulator by the logger.
-
-#------------------------------------------------------------------------------
-
-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_resizable(False)
-        self.set_title(WINDOW_TITLE_BASE + " - 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 = 9, columns = 14)
-        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, 11, 0, 1)
-
-        self._antiIceOn = gtk.Label("ANTI-ICE")
-        table.attach(self._antiIceOn, 11, 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:", 13)
-        table.attach(label, 10, 11, 1, 2)
-        table.attach(self._vs, 11, 12, 1, 2)
-
-        (label, self._ias) = self._createLabeledEntry("IAS:", 11)
-        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, 2, 5, 6)
-
-        self._strobeLightsOn = gtk.Label("STROBE")
-        table.attach(self._strobeLightsOn, 2, 3, 5, 6)
-
-        self._landingLightsOn = gtk.Label("LANDING")
-        table.attach(self._landingLightsOn, 3, 4, 5, 6)
-
-        self._pitotHeatOn = gtk.Label("PITOT HEAT")
-        table.attach(self._pitotHeatOn, 4, 5, 5, 6)
-
-        self._parking = gtk.Label("PARKING")
-        table.attach(self._parking, 5, 6, 5, 6)
-
-        self._gearControlDown = gtk.Label("GEAR LEVER DOWN")
-        table.attach(self._gearControlDown, 6, 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)
-
-        (label, self._elevatorTrim) = self._createLabeledEntry("Trim:", 5)
-        table.attach(label, 10, 11, 6, 7)
-        table.attach(self._elevatorTrim, 11, 12, 6, 7)
-
-        self._apMaster = gtk.Label("AP MASTER")
-        table.attach(self._apMaster, 0, 1, 7, 8)
-
-        self._apHeadingHold = gtk.Label("AP HDG HOLD")
-        table.attach(self._apHeadingHold, 1, 2, 7, 8)
-
-        (label, self._apHeading) = self._createLabeledEntry("AP HDG:", 5)
-        table.attach(label, 2, 3, 7, 8)
-        table.attach(self._apHeading, 3, 4, 7, 8)
-
-        self._xpdrC = gtk.Label("XPDR C")
-        table.attach(self._xpdrC, 4, 5, 7, 8)
-
-        self._apAltitudeHold = gtk.Label("AP ALT HOLD")
-        table.attach(self._apAltitudeHold, 5, 6, 7, 8)
-
-        (label, self._apAltitude) = self._createLabeledEntry("AP ALT:", 10)
-        table.attach(label, 6, 7, 7, 8)
-        table.attach(self._apAltitude, 7, 8, 7, 8)
-
-        (label, self._adf1) = self._createLabeledEntry("ADF1:", 5)
-        table.attach(label, 8, 9, 7, 8)
-        table.attach(self._adf1, 9, 10, 7, 8)
-
-        (label, self._adf2) = self._createLabeledEntry("ADF2:", 5)
-        table.attach(label, 10, 11, 7, 8)
-        table.attach(self._adf2, 11, 12, 7, 8)
-
-        (label, self._qnh) = self._createLabeledEntry("QNH:", 7)
-        table.attach(label, 0, 1, 8, 9)
-        table.attach(self._qnh, 1, 2, 8, 9)
-
-        (label, self._cog) = self._createLabeledEntry("CoG:", 7)
-        table.attach(label, 2, 3, 8, 9)
-        table.attach(self._cog, 3, 4, 8, 9)
-
-        (label, self._ils) = self._createLabeledEntry("ILS:", 7)
-        table.attach(label, 4, 5, 8, 9)
-        table.attach(self._ils, 5, 6, 8, 9)
-
-        (label, self._crs1) = self._createLabeledEntry("CRS1:", 7)
-        table.attach(label, 6, 7, 8, 9)
-        table.attach(self._crs1, 7, 8, 8, 9)
-
-        (label, self._crs2) = self._createLabeledEntry("CRS2:", 7)
-        table.attach(label, 8, 9, 8, 9)
-        table.attach(self._crs2, 9, 10, 8, 9)
-
-        (label, self._crsi) = self._createLabeledEntry("CRSI:", 7)
-        table.attach(label, 10, 11, 8, 9)
-        table.attach(self._crsi, 11, 12, 8, 9)
-
-        alignment.add(table)
-
-        self.add(alignment)
-
-        self._previousState = None
-
-        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._antiIceOn.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_text("LANDING")
-            self._landingLightsOn.set_sensitive(False)
-            self._pitotHeatOn.set_sensitive(False)
-            self._parking.set_sensitive(False)
-            self._gearControlDown.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("-")
-            self._elevatorTrim.set_text("-")
-            self._apMaster.set_sensitive(False)
-            self._apHeadingHold.set_sensitive(False)
-            self._apHeading.set_text("-")
-            self._xpdrC.set_sensitive(False)
-            self._apAltitudeHold.set_sensitive(False)
-            self._apAltitude.set_text("-")
-            self._adf1.set_text("-")
-            self._adf2.set_text("-")
-            self._qnh.set_text("-")
-            self._cog.set_text("-")
-            self._ils.set_text("-")
-            self._crs1.set_text("-")
-            self._crs2.set_text("-")
-            self._crsi.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._antiIceOn.set_sensitive(aircraftState.antiIceOn is True)
-            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 (%.0f)" % (aircraftState.vs,
-                                               aircraftState.smoothedVS))
-            self._ias.set_text("%.0f (%.0f)" % (aircraftState.ias,
-                                                aircraftState.smoothedIAS))
-            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("-" if aircraftState.nav1 is None else aircraftState.nav1)
-            self._nav2.set_text("-" if aircraftState.nav2 is None else aircraftState.nav2)
-            self._qnh.set_text("%.0f" % (aircraftState.qnh,))
-
-            fuelStr = ""
-            for (_tank, fuel) in aircraftState.fuel:
-                if fuelStr: fuelStr += ", "
-                fuelStr += "%.0f" % (fuel,)
-            self._fuel.set_text(fuelStr)
-
-            if aircraftState.n1 is not None:
-                n1Str = ""
-                for n1 in aircraftState.n1:
-                    if n1Str: n1Str += ", "
-                    n1Str += "%.0f" % (n1,)
-            elif aircraftState.rpm is not None:
-                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 is True)
-            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn is True)
-            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn is True)
-
-            if self._previousState is None or \
-               ((self._previousState.landingLightsOn is None)!=
-                (aircraftState.landingLightsOn is None)):
-                if aircraftState.landingLightsOn is None:
-                    self._landingLightsOn.set_markup('<span strikethrough="true">LANDING</span>')
-                else:
-                    self._landingLightsOn.set_text("LANDING")
-            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn is True)
-
-            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn is True)
-            self._parking.set_sensitive(aircraftState.parking)
-            self._gearControlDown.set_sensitive(aircraftState.gearControlDown)
-            self._gearsDown.set_sensitive(aircraftState.gearsDown)
-            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed is True)
-            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)))
-            self._elevatorTrim.set_text("%.2f" % (aircraftState.elevatorTrim,))
-            self._apMaster.set_sensitive(aircraftState.apMaster is True)
-            self._apHeadingHold.set_sensitive(aircraftState.apHeadingHold is True)
-            self._apHeading.set_text("-" if aircraftState.apHeading is None
-                                     else "%03.0f" % (aircraftState.apHeading,))
-            self._xpdrC.set_sensitive(aircraftState.xpdrC is True)
-            self._apAltitudeHold.set_sensitive(aircraftState.apAltitudeHold is True)
-            self._apAltitude.set_text("-" if aircraftState.apAltitude is None
-                                      else "%5.0f" % (aircraftState.apAltitude,))
-
-            self._adf1.set_text("-" if aircraftState.adf1 is None else aircraftState.adf1)
-            self._adf2.set_text("-" if aircraftState.adf2 is None else aircraftState.adf2)
-            self._cog.set_text("%.2f%%" % (aircraftState.cog*100.0,))
-
-            self._ils.set_text("-" if aircraftState.ils is None else aircraftState.ils)
-
-            self._crs1.set_text("-" if aircraftState.nav1_obs is None else str(aircraftState.nav1_obs))
-            self._crs2.set_text("-" if aircraftState.nav2_obs is None else str(aircraftState.nav2_obs))
-            self._crsi.set_text("-" if aircraftState.ils_obs is None else str(aircraftState.ils_obs))
-
-#------------------------------------------------------------------------------
Index: src/mlx/gui/pirep.py
===================================================================
--- src/mlx/gui/pirep.py	(revision 437)
+++ src/mlx/gui/pirep.py	(revision 437)
@@ -0,0 +1,616 @@
+
+from common import *
+
+from mlx.pirep import PIREP
+from mlx.const import *
+
+import time
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.pirep
+#
+# The detailed PIREP viewer.
+#
+# The \ref PIREPViewer class is a dialog displaying all information found in a
+# PIREP. It consists of three tabs. The Data tab displays the simple,
+# itemizable data. The Comments & defects tab contains the flight comments and
+# defects, while the Log tab contains the flight log collected by the
+# \ref mlx.logger.Logger "logger".
+
+#------------------------------------------------------------------------------
+
+class PIREPViewer(gtk.Dialog):
+    """The dialog for PIREP viewing."""
+    @staticmethod
+    def createFrame(label):
+        """Create a frame with the given label.
+
+        The frame will contain an alignment to properly distance the
+        insides. The alignment will contain a VBox to contain the real
+        contents. 
+
+        The function returns a tuple with the following items:
+        - the frame,
+        - the inner VBox."""
+        frame = gtk.Frame(label = label)
+
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 1.0, yscale = 1.0)
+        frame.add(alignment)
+        alignment.set_padding(padding_top = 4, padding_bottom = 4,
+                              padding_left = 4, padding_right = 4)
+        box = gtk.VBox()
+        alignment.add(box)
+        
+        return (frame, box)
+
+    @staticmethod
+    def getLabel(text):
+        """Get a bold label with the given text."""
+        label = gtk.Label("<b>" + text + "</b>")
+        label.set_use_markup(True)
+        label.set_alignment(0.0, 0.5)
+        return label
+
+    @staticmethod
+    def getDataLabel(width = None, xAlignment = 0.0):
+        """Get a bold label with the given text."""
+        label = gtk.Label()
+        if width is not None:
+            label.set_width_chars(width)
+        label.set_alignment(xAlignment, 0.5)
+        return label
+
+    @staticmethod
+    def getTextWindow(heightRequest = 40):
+        """Get a scrollable text window.
+        
+        Returns a tuple of the following items:
+        - the window,
+        - the text view."""
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.set_shadow_type(SHADOW_IN)
+        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
+
+        textView = gtk.TextView()
+        textView.set_wrap_mode(WRAP_WORD)
+        textView.set_editable(False)
+        textView.set_cursor_visible(False)
+        textView.set_size_request(-1, heightRequest)
+        scrolledWindow.add(textView)
+
+        return (scrolledWindow, textView)
+
+    @staticmethod
+    def tableAttach(table, column, row, labelText, width = None,
+                    dataLabelXAlignment = 0.0):
+        """Attach a labeled data to the given column and row of the
+        table. 
+        
+        If width is given, that will be the width of the data
+        label.
+
+        Returns the data label attached."""
+        dataBox = gtk.HBox()
+        table.attach(dataBox, column, column+1, row, row+1)
+        
+        dataLabel = PIREPViewer.addLabeledData(dataBox, labelText,
+                                               width = width)
+        dataLabel.set_alignment(dataLabelXAlignment, 0.5)
+
+        return dataLabel
+
+    @staticmethod
+    def addLabeledData(hBox, labelText, width = None, dataPadding = 8):
+        """Add a label and a data label to the given HBox.
+        
+        Returnsd the data label."""
+        label = PIREPViewer.getLabel(labelText)
+        hBox.pack_start(label, False, False, 0)
+                
+        dataLabel = PIREPViewer.getDataLabel(width = width)
+        hBox.pack_start(dataLabel, False, False, dataPadding)
+
+        return dataLabel
+
+    @staticmethod
+    def addVFiller(vBox, height = 4):
+        """Add a filler to the given vertical box."""
+        filler = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                               xscale = 1.0, yscale = 1.0)
+        filler.set_size_request(-1, height)
+        vBox.pack_start(filler, False, False, 0)
+        
+    @staticmethod
+    def timestamp2text(label, timestamp):
+        """Convert the given timestamp into a text containing the hour
+        and minute in UTC and put that text into the given label."""
+        tm = time.gmtime(timestamp)
+        label.set_text("%02d:%02d" % (tm.tm_hour, tm.tm_min))
+
+    def __init__(self, gui):
+        """Construct the PIREP viewer."""
+        super(PIREPViewer, self).__init__(title = WINDOW_TITLE_BASE +
+                                          " - " +
+                                          xstr("pirepView_title"),
+                                          parent = gui.mainWindow)
+                                          
+        self.set_resizable(False)
+
+        self._gui = gui
+        
+        contentArea = self.get_content_area()
+
+        self._notebook = gtk.Notebook()
+        contentArea.pack_start(self._notebook, False, False, 4)
+        
+        dataTab = self._buildDataTab()
+        label = gtk.Label(xstr("pirepView_tab_data"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("pirepView_tab_data_tooltip"))
+        self._notebook.append_page(dataTab, label)
+        
+        commentsTab = self._buildCommentsTab()
+        label = gtk.Label(xstr("pirepView_tab_comments"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("pirepView_tab_comments_tooltip"))
+        self._notebook.append_page(commentsTab, label)
+        
+        logTab = self._buildLogTab()
+        label = gtk.Label(xstr("pirepView_tab_log"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("pirepView_tab_log_tooltip"))
+        self._notebook.append_page(logTab, label)
+
+        self._okButton = self.add_button(xstr("button_ok"), RESPONSETYPE_OK)
+        self._okButton.set_can_default(True)
+        
+    def setPIREP(self, pirep):
+        """Setup the data in the dialog from the given PIREP."""
+        bookedFlight = pirep.bookedFlight
+
+        self._callsign.set_text(bookedFlight.callsign)
+        self._tailNumber.set_text(bookedFlight.tailNumber)
+        aircraftType = xstr("aircraft_" + icaoCodes[bookedFlight.aircraftType].lower())
+        self._aircraftType.set_text(aircraftType)
+        
+        self._departureICAO.set_text(bookedFlight.departureICAO)
+        self._departureTime.set_text("%02d:%02d" % \
+                                     (bookedFlight.departureTime.hour,
+                                      bookedFlight.departureTime.minute))
+
+        self._arrivalICAO.set_text(bookedFlight.arrivalICAO)
+        self._arrivalTime.set_text("%02d:%02d" % \
+                                   (bookedFlight.arrivalTime.hour,
+                                    bookedFlight.arrivalTime.minute))
+
+        self._numPassengers.set_text(str(bookedFlight.numPassengers))
+        self._numCrew.set_text(str(bookedFlight.numCrew))
+        self._bagWeight.set_text(str(bookedFlight.bagWeight))
+        self._cargoWeight.set_text(str(bookedFlight.cargoWeight))
+        self._mailWeight.set_text(str(bookedFlight.mailWeight))
+        
+        self._route.get_buffer().set_text(bookedFlight.route)
+
+        self._filedCruiseLevel.set_text("FL" + str(pirep.filedCruiseAltitude/100))
+
+        if pirep.cruiseAltitude != pirep.filedCruiseAltitude:
+            self._modifiedCruiseLevel.set_text("FL" + str(pirep.cruiseAltitude/100))
+        else:
+            self._modifiedCruiseLevel.set_text("-")
+
+        self._userRoute.get_buffer().set_text(pirep.route)
+
+        self._departureMETAR.get_buffer().set_text(pirep.departureMETAR)
+
+        self._arrivalMETAR.get_buffer().set_text(pirep.arrivalMETAR)
+        self._departureRunway.set_text(pirep.departureRunway)
+        self._sid.set_text(pirep.sid)
+
+        self._star.set_text("-" if pirep.star is None else pirep.star)
+        self._transition.set_text("-" if pirep.transition is None else pirep.transition)
+        self._approachType.set_text(pirep.approachType)
+        self._arrivalRunway.set_text(pirep.arrivalRunway)
+
+        PIREPViewer.timestamp2text(self._blockTimeStart, pirep.blockTimeStart)
+        PIREPViewer.timestamp2text(self._blockTimeEnd, pirep.blockTimeEnd)
+        PIREPViewer.timestamp2text(self._flightTimeStart, pirep.flightTimeStart)
+        PIREPViewer.timestamp2text(self._flightTimeEnd, pirep.flightTimeEnd)
+        
+        self._flownDistance.set_text("%.1f" % (pirep.flownDistance,))
+        self._fuelUsed.set_text("%.0f" % (pirep.fuelUsed,))
+
+        rating = pirep.rating
+        if rating<0:
+            self._rating.set_markup('<b><span foreground="red">NO GO</span></b>')
+        else:
+            self._rating.set_text("%.1f %%" % (rating,))
+
+        self._flownNumCrew.set_text("%d" % (pirep.numCrew,))
+        self._flownNumPassengers.set_text("%d" % (pirep.numPassengers,))
+        self._flownBagWeight.set_text("%.0f" % (pirep.bagWeight,))
+        self._flownCargoWeight.set_text("%.0f" % (pirep.cargoWeight,))
+        self._flownMailWeight.set_text("%.0f" % (pirep.mailWeight,))
+        self._flightType.set_text(xstr("flighttype_" + 
+                                       flightType2string(pirep.flightType)))
+        self._online.set_text(xstr("pirepView_" +
+                                   ("yes" if pirep.online else "no")))
+
+        delayCodes = ""
+        for code in pirep.delayCodes:
+            if delayCodes: delayCodes += ", "
+            delayCodes += code
+        
+        self._delayCodes.get_buffer().set_text(delayCodes)        
+
+        self._comments.get_buffer().set_text(pirep.comments)
+        self._flightDefects.get_buffer().set_text(pirep.flightDefects)
+
+        logBuffer = self._log.get_buffer()
+        logBuffer.set_text("")
+        lineIndex = 0
+        for (timeStr, line) in pirep.logLines:
+            isFault = lineIndex in pirep.faultLineIndexes
+            appendTextBuffer(logBuffer,
+                             formatFlightLogLine(timeStr, line),
+                             isFault = isFault)
+            lineIndex += 1
+
+        self._notebook.set_current_page(0)
+        self._okButton.grab_default()
+
+    def _buildDataTab(self):
+        """Build the data tab of the viewer."""
+        table = gtk.Table(1, 2)
+        table.set_row_spacings(4)
+        table.set_col_spacings(16)
+        table.set_homogeneous(True)
+
+        box1 = gtk.VBox()
+        table.attach(box1, 0, 1, 0, 1)
+        
+        box2 = gtk.VBox()
+        table.attach(box2, 1, 2, 0, 1)
+        
+        flightFrame = self._buildFlightFrame()
+        box1.pack_start(flightFrame, False, False, 4)
+
+        routeFrame = self._buildRouteFrame()
+        box1.pack_start(routeFrame, False, False, 4)
+
+        departureFrame = self._buildDepartureFrame()
+        box2.pack_start(departureFrame, True, True, 4)
+
+        arrivalFrame = self._buildArrivalFrame()
+        box2.pack_start(arrivalFrame, True, True, 4)
+
+        statisticsFrame = self._buildStatisticsFrame()
+        box2.pack_start(statisticsFrame, False, False, 4)
+
+        miscellaneousFrame = self._buildMiscellaneousFrame()
+        box1.pack_start(miscellaneousFrame, False, False, 4)
+
+        return table
+
+    def _buildFlightFrame(self):
+        """Build the frame for the flight data."""
+        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_flight"))
+        
+        dataBox = gtk.HBox()
+        mainBox.pack_start(dataBox, False, False, 0)
+        
+        self._callsign = \
+            PIREPViewer.addLabeledData(dataBox,
+                                       xstr("pirepView_callsign"),
+                                       width = 8)
+
+        self._tailNumber = \
+            PIREPViewer.addLabeledData(dataBox,
+                                       xstr("pirepView_tailNumber"),
+                                       width = 7)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        dataBox = gtk.HBox()
+        mainBox.pack_start(dataBox, False, False, 0)
+
+        self._aircraftType = \
+            PIREPViewer.addLabeledData(dataBox,
+                                       xstr("pirepView_aircraftType"),
+                                       width = 25)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        table = gtk.Table(3, 2)
+        mainBox.pack_start(table, False, False, 0)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)        
+
+        self._departureICAO = \
+            PIREPViewer.tableAttach(table, 0, 0,
+                                    xstr("pirepView_departure"),
+                                    width = 5)
+
+        self._departureTime = \
+            PIREPViewer.tableAttach(table, 1, 0,
+                                    xstr("pirepView_departure_time"),
+                                    width = 6)
+
+        self._arrivalICAO = \
+            PIREPViewer.tableAttach(table, 0, 1,
+                                    xstr("pirepView_arrival"),
+                                    width = 5)
+
+        self._arrivalTime = \
+            PIREPViewer.tableAttach(table, 1, 1,
+                                    xstr("pirepView_arrival_time"),
+                                    width = 6)
+
+        table = gtk.Table(3, 2)
+        mainBox.pack_start(table, False, False, 0)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)        
+
+        self._numPassengers = \
+            PIREPViewer.tableAttach(table, 0, 0,
+                                    xstr("pirepView_numPassengers"),
+                                    width = 4)
+
+        self._numCrew = \
+            PIREPViewer.tableAttach(table, 1, 0,
+                                    xstr("pirepView_numCrew"),
+                                    width = 3)
+
+        self._bagWeight = \
+            PIREPViewer.tableAttach(table, 0, 1, 
+                                    xstr("pirepView_bagWeight"),
+                                    width = 5)
+
+        self._cargoWeight = \
+            PIREPViewer.tableAttach(table, 1, 1, 
+                                    xstr("pirepView_cargoWeight"),
+                                    width = 5)
+
+        self._mailWeight = \
+            PIREPViewer.tableAttach(table, 2, 1, 
+                                    xstr("pirepView_mailWeight"),
+                                    width = 5)
+            
+        PIREPViewer.addVFiller(mainBox)
+
+        mainBox.pack_start(PIREPViewer.getLabel(xstr("pirepView_route")),
+                           False, False, 0)
+        
+        (routeWindow, self._route) = PIREPViewer.getTextWindow()
+        mainBox.pack_start(routeWindow, False, False, 0)
+
+        return frame
+
+    def _buildRouteFrame(self):
+        """Build the frame for the user-specified route and flight
+        level."""
+        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_route"))
+
+        levelBox = gtk.HBox()
+        mainBox.pack_start(levelBox, False, False, 0)
+        
+        self._filedCruiseLevel = \
+            PIREPViewer.addLabeledData(levelBox, 
+                                       xstr("pirepView_filedCruiseLevel"),
+                                       width = 6)
+
+        self._modifiedCruiseLevel = \
+            PIREPViewer.addLabeledData(levelBox,
+                                       xstr("pirepView_modifiedCruiseLevel"),
+                                       width = 6)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        (routeWindow, self._userRoute) = PIREPViewer.getTextWindow()
+        mainBox.pack_start(routeWindow, False, False, 0)
+
+        return frame
+
+    def _buildDepartureFrame(self):
+        """Build the frame for the departure data."""        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_departure"))
+
+        mainBox.pack_start(PIREPViewer.getLabel("METAR:"),
+                           False, False, 0)
+        (metarWindow, self._departureMETAR) = \
+            PIREPViewer.getTextWindow(heightRequest = -1)
+        mainBox.pack_start(metarWindow, True, True, 0)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        dataBox = gtk.HBox()
+        mainBox.pack_start(dataBox, False, False, 0)
+        
+        self._departureRunway = \
+            PIREPViewer.addLabeledData(dataBox,
+                                       xstr("pirepView_runway"),
+                                       width = 5)
+
+        self._sid = \
+            PIREPViewer.addLabeledData(dataBox,
+                                       xstr("pirepView_sid"),
+                                       width = 12)
+
+        return frame
+                           
+    def _buildArrivalFrame(self):
+        """Build the frame for the arrival data."""        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_arrival"))
+
+        mainBox.pack_start(PIREPViewer.getLabel("METAR:"),
+                           False, False, 0)
+        (metarWindow, self._arrivalMETAR) = \
+            PIREPViewer.getTextWindow(heightRequest = -1)
+        mainBox.pack_start(metarWindow, True, True, 0)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        table = gtk.Table(2, 2)
+        mainBox.pack_start(table, False, False, 0)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)        
+
+        self._star = \
+            PIREPViewer.tableAttach(table, 0, 0,
+                                    xstr("pirepView_star"),
+                                    width = 12)
+
+        self._transition = \
+            PIREPViewer.tableAttach(table, 1, 0,
+                                    xstr("pirepView_transition"),
+                                    width = 12)
+                           
+        self._approachType = \
+            PIREPViewer.tableAttach(table, 0, 1,
+                                    xstr("pirepView_approachType"),
+                                    width = 7)
+                           
+        self._arrivalRunway = \
+            PIREPViewer.tableAttach(table, 1, 1, 
+                                    xstr("pirepView_runway"),
+                                    width = 5)
+                           
+        return frame
+
+    def _buildStatisticsFrame(self):
+        """Build the frame for the statistics data."""        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_statistics"))
+
+        table = gtk.Table(4, 2)
+        mainBox.pack_start(table, False, False, 0)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)
+        table.set_homogeneous(False)
+        
+        self._blockTimeStart = \
+            PIREPViewer.tableAttach(table, 0, 0,
+                                    xstr("pirepView_blockTimeStart"),
+                                    width = 6)
+        
+        self._blockTimeEnd = \
+            PIREPViewer.tableAttach(table, 1, 0,
+                                    xstr("pirepView_blockTimeEnd"),
+                                    width = 8)
+
+        self._flightTimeStart = \
+            PIREPViewer.tableAttach(table, 0, 1,
+                                    xstr("pirepView_flightTimeStart"),
+                                    width = 6)
+        
+        self._flightTimeEnd = \
+            PIREPViewer.tableAttach(table, 1, 1, 
+                                    xstr("pirepView_flightTimeEnd"),
+                                    width = 6)
+
+        self._flownDistance = \
+            PIREPViewer.tableAttach(table, 0, 2,
+                                    xstr("pirepView_flownDistance"),
+                                    width = 8)
+        
+        self._fuelUsed = \
+            PIREPViewer.tableAttach(table, 1, 2,
+                                    xstr("pirepView_fuelUsed"),
+                                    width = 6)
+        
+        self._rating = \
+            PIREPViewer.tableAttach(table, 0, 3,
+                                    xstr("pirepView_rating"),
+                                    width = 7)
+        return frame
+
+    def _buildMiscellaneousFrame(self):
+        """Build the frame for the miscellaneous data."""        
+        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_miscellaneous"))
+
+        table = gtk.Table(3, 2)
+        mainBox.pack_start(table, False, False, 0)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)        
+        
+        self._flownNumPassengers = \
+            PIREPViewer.tableAttach(table, 0, 0,
+                                    xstr("pirepView_numPassengers"),
+                                    width = 4)
+
+        self._flownNumCrew = \
+            PIREPViewer.tableAttach(table, 1, 0,
+                                    xstr("pirepView_numCrew"),
+                                    width = 3)
+
+        self._flownBagWeight = \
+            PIREPViewer.tableAttach(table, 0, 1,
+                                    xstr("pirepView_bagWeight"),
+                                    width = 5)
+
+        self._flownCargoWeight = \
+            PIREPViewer.tableAttach(table, 1, 1,
+                                    xstr("pirepView_cargoWeight"),
+                                    width = 6)
+
+        self._flownMailWeight = \
+            PIREPViewer.tableAttach(table, 2, 1,
+                                    xstr("pirepView_mailWeight"),
+                                    width = 5)
+
+        self._flightType = \
+            PIREPViewer.tableAttach(table, 0, 2,
+                                    xstr("pirepView_flightType"),
+                                    width = 15)
+            
+        self._online = \
+            PIREPViewer.tableAttach(table, 1, 2,
+                                    xstr("pirepView_online"),
+                                    width = 5)
+
+        PIREPViewer.addVFiller(mainBox)
+
+        mainBox.pack_start(PIREPViewer.getLabel(xstr("pirepView_delayCodes")),
+                           False, False, 0)
+    
+        (textWindow, self._delayCodes) = PIREPViewer.getTextWindow()
+        mainBox.pack_start(textWindow, False, False, 0)
+
+        return frame        
+
+    def _buildCommentsTab(self):
+        """Build the tab with the comments and flight defects."""
+        table = gtk.Table(2, 1)
+        table.set_col_spacings(16)
+
+        (frame, commentsBox) = \
+            PIREPViewer.createFrame(xstr("pirepView_comments"))
+        table.attach(frame, 0, 1, 0, 1)
+
+        (commentsWindow, self._comments) = \
+            PIREPViewer.getTextWindow(heightRequest = -1)
+        commentsBox.pack_start(commentsWindow, True, True, 0)
+
+        (frame, flightDefectsBox) = \
+            PIREPViewer.createFrame(xstr("pirepView_flightDefects"))
+        table.attach(frame, 1, 2, 0, 1)
+
+        (flightDefectsWindow, self._flightDefects) = \
+            PIREPViewer.getTextWindow(heightRequest = -1)
+        flightDefectsBox.pack_start(flightDefectsWindow, True, True, 0)
+        
+        return table
+
+    def _buildLogTab(self):
+        """Build the log tab."""
+        mainBox = gtk.VBox()
+
+        (logWindow, self._log) = PIREPViewer.getTextWindow(heightRequest = -1)
+        addFaultTag(self._log.get_buffer())
+        mainBox.pack_start(logWindow, True, True, 0)
+        
+        return mainBox
+        
+#------------------------------------------------------------------------------
Index: src/mlx/gui/pirep.py
===================================================================
--- src/mlx/gui/pirep.py	(revision 437)
+++ 	(revision )
@@ -1,616 +1,0 @@
-
-from common import *
-
-from mlx.pirep import PIREP
-from mlx.const import *
-
-import time
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.pirep
-#
-# The detailed PIREP viewer.
-#
-# The \ref PIREPViewer class is a dialog displaying all information found in a
-# PIREP. It consists of three tabs. The Data tab displays the simple,
-# itemizable data. The Comments & defects tab contains the flight comments and
-# defects, while the Log tab contains the flight log collected by the
-# \ref mlx.logger.Logger "logger".
-
-#------------------------------------------------------------------------------
-
-class PIREPViewer(gtk.Dialog):
-    """The dialog for PIREP viewing."""
-    @staticmethod
-    def createFrame(label):
-        """Create a frame with the given label.
-
-        The frame will contain an alignment to properly distance the
-        insides. The alignment will contain a VBox to contain the real
-        contents. 
-
-        The function returns a tuple with the following items:
-        - the frame,
-        - the inner VBox."""
-        frame = gtk.Frame(label = label)
-
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 1.0, yscale = 1.0)
-        frame.add(alignment)
-        alignment.set_padding(padding_top = 4, padding_bottom = 4,
-                              padding_left = 4, padding_right = 4)
-        box = gtk.VBox()
-        alignment.add(box)
-        
-        return (frame, box)
-
-    @staticmethod
-    def getLabel(text):
-        """Get a bold label with the given text."""
-        label = gtk.Label("<b>" + text + "</b>")
-        label.set_use_markup(True)
-        label.set_alignment(0.0, 0.5)
-        return label
-
-    @staticmethod
-    def getDataLabel(width = None, xAlignment = 0.0):
-        """Get a bold label with the given text."""
-        label = gtk.Label()
-        if width is not None:
-            label.set_width_chars(width)
-        label.set_alignment(xAlignment, 0.5)
-        return label
-
-    @staticmethod
-    def getTextWindow(heightRequest = 40):
-        """Get a scrollable text window.
-        
-        Returns a tuple of the following items:
-        - the window,
-        - the text view."""
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.set_shadow_type(SHADOW_IN)
-        scrolledWindow.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
-
-        textView = gtk.TextView()
-        textView.set_wrap_mode(WRAP_WORD)
-        textView.set_editable(False)
-        textView.set_cursor_visible(False)
-        textView.set_size_request(-1, heightRequest)
-        scrolledWindow.add(textView)
-
-        return (scrolledWindow, textView)
-
-    @staticmethod
-    def tableAttach(table, column, row, labelText, width = None,
-                    dataLabelXAlignment = 0.0):
-        """Attach a labeled data to the given column and row of the
-        table. 
-        
-        If width is given, that will be the width of the data
-        label.
-
-        Returns the data label attached."""
-        dataBox = gtk.HBox()
-        table.attach(dataBox, column, column+1, row, row+1)
-        
-        dataLabel = PIREPViewer.addLabeledData(dataBox, labelText,
-                                               width = width)
-        dataLabel.set_alignment(dataLabelXAlignment, 0.5)
-
-        return dataLabel
-
-    @staticmethod
-    def addLabeledData(hBox, labelText, width = None, dataPadding = 8):
-        """Add a label and a data label to the given HBox.
-        
-        Returnsd the data label."""
-        label = PIREPViewer.getLabel(labelText)
-        hBox.pack_start(label, False, False, 0)
-                
-        dataLabel = PIREPViewer.getDataLabel(width = width)
-        hBox.pack_start(dataLabel, False, False, dataPadding)
-
-        return dataLabel
-
-    @staticmethod
-    def addVFiller(vBox, height = 4):
-        """Add a filler to the given vertical box."""
-        filler = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                               xscale = 1.0, yscale = 1.0)
-        filler.set_size_request(-1, height)
-        vBox.pack_start(filler, False, False, 0)
-        
-    @staticmethod
-    def timestamp2text(label, timestamp):
-        """Convert the given timestamp into a text containing the hour
-        and minute in UTC and put that text into the given label."""
-        tm = time.gmtime(timestamp)
-        label.set_text("%02d:%02d" % (tm.tm_hour, tm.tm_min))
-
-    def __init__(self, gui):
-        """Construct the PIREP viewer."""
-        super(PIREPViewer, self).__init__(title = WINDOW_TITLE_BASE +
-                                          " - " +
-                                          xstr("pirepView_title"),
-                                          parent = gui.mainWindow)
-                                          
-        self.set_resizable(False)
-
-        self._gui = gui
-        
-        contentArea = self.get_content_area()
-
-        self._notebook = gtk.Notebook()
-        contentArea.pack_start(self._notebook, False, False, 4)
-        
-        dataTab = self._buildDataTab()
-        label = gtk.Label(xstr("pirepView_tab_data"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("pirepView_tab_data_tooltip"))
-        self._notebook.append_page(dataTab, label)
-        
-        commentsTab = self._buildCommentsTab()
-        label = gtk.Label(xstr("pirepView_tab_comments"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("pirepView_tab_comments_tooltip"))
-        self._notebook.append_page(commentsTab, label)
-        
-        logTab = self._buildLogTab()
-        label = gtk.Label(xstr("pirepView_tab_log"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("pirepView_tab_log_tooltip"))
-        self._notebook.append_page(logTab, label)
-
-        self._okButton = self.add_button(xstr("button_ok"), RESPONSETYPE_OK)
-        self._okButton.set_can_default(True)
-        
-    def setPIREP(self, pirep):
-        """Setup the data in the dialog from the given PIREP."""
-        bookedFlight = pirep.bookedFlight
-
-        self._callsign.set_text(bookedFlight.callsign)
-        self._tailNumber.set_text(bookedFlight.tailNumber)
-        aircraftType = xstr("aircraft_" + icaoCodes[bookedFlight.aircraftType].lower())
-        self._aircraftType.set_text(aircraftType)
-        
-        self._departureICAO.set_text(bookedFlight.departureICAO)
-        self._departureTime.set_text("%02d:%02d" % \
-                                     (bookedFlight.departureTime.hour,
-                                      bookedFlight.departureTime.minute))
-
-        self._arrivalICAO.set_text(bookedFlight.arrivalICAO)
-        self._arrivalTime.set_text("%02d:%02d" % \
-                                   (bookedFlight.arrivalTime.hour,
-                                    bookedFlight.arrivalTime.minute))
-
-        self._numPassengers.set_text(str(bookedFlight.numPassengers))
-        self._numCrew.set_text(str(bookedFlight.numCrew))
-        self._bagWeight.set_text(str(bookedFlight.bagWeight))
-        self._cargoWeight.set_text(str(bookedFlight.cargoWeight))
-        self._mailWeight.set_text(str(bookedFlight.mailWeight))
-        
-        self._route.get_buffer().set_text(bookedFlight.route)
-
-        self._filedCruiseLevel.set_text("FL" + str(pirep.filedCruiseAltitude/100))
-
-        if pirep.cruiseAltitude != pirep.filedCruiseAltitude:
-            self._modifiedCruiseLevel.set_text("FL" + str(pirep.cruiseAltitude/100))
-        else:
-            self._modifiedCruiseLevel.set_text("-")
-
-        self._userRoute.get_buffer().set_text(pirep.route)
-
-        self._departureMETAR.get_buffer().set_text(pirep.departureMETAR)
-
-        self._arrivalMETAR.get_buffer().set_text(pirep.arrivalMETAR)
-        self._departureRunway.set_text(pirep.departureRunway)
-        self._sid.set_text(pirep.sid)
-
-        self._star.set_text("-" if pirep.star is None else pirep.star)
-        self._transition.set_text("-" if pirep.transition is None else pirep.transition)
-        self._approachType.set_text(pirep.approachType)
-        self._arrivalRunway.set_text(pirep.arrivalRunway)
-
-        PIREPViewer.timestamp2text(self._blockTimeStart, pirep.blockTimeStart)
-        PIREPViewer.timestamp2text(self._blockTimeEnd, pirep.blockTimeEnd)
-        PIREPViewer.timestamp2text(self._flightTimeStart, pirep.flightTimeStart)
-        PIREPViewer.timestamp2text(self._flightTimeEnd, pirep.flightTimeEnd)
-        
-        self._flownDistance.set_text("%.1f" % (pirep.flownDistance,))
-        self._fuelUsed.set_text("%.0f" % (pirep.fuelUsed,))
-
-        rating = pirep.rating
-        if rating<0:
-            self._rating.set_markup('<b><span foreground="red">NO GO</span></b>')
-        else:
-            self._rating.set_text("%.1f %%" % (rating,))
-
-        self._flownNumCrew.set_text("%d" % (pirep.numCrew,))
-        self._flownNumPassengers.set_text("%d" % (pirep.numPassengers,))
-        self._flownBagWeight.set_text("%.0f" % (pirep.bagWeight,))
-        self._flownCargoWeight.set_text("%.0f" % (pirep.cargoWeight,))
-        self._flownMailWeight.set_text("%.0f" % (pirep.mailWeight,))
-        self._flightType.set_text(xstr("flighttype_" + 
-                                       flightType2string(pirep.flightType)))
-        self._online.set_text(xstr("pirepView_" +
-                                   ("yes" if pirep.online else "no")))
-
-        delayCodes = ""
-        for code in pirep.delayCodes:
-            if delayCodes: delayCodes += ", "
-            delayCodes += code
-        
-        self._delayCodes.get_buffer().set_text(delayCodes)        
-
-        self._comments.get_buffer().set_text(pirep.comments)
-        self._flightDefects.get_buffer().set_text(pirep.flightDefects)
-
-        logBuffer = self._log.get_buffer()
-        logBuffer.set_text("")
-        lineIndex = 0
-        for (timeStr, line) in pirep.logLines:
-            isFault = lineIndex in pirep.faultLineIndexes
-            appendTextBuffer(logBuffer,
-                             formatFlightLogLine(timeStr, line),
-                             isFault = isFault)
-            lineIndex += 1
-
-        self._notebook.set_current_page(0)
-        self._okButton.grab_default()
-
-    def _buildDataTab(self):
-        """Build the data tab of the viewer."""
-        table = gtk.Table(1, 2)
-        table.set_row_spacings(4)
-        table.set_col_spacings(16)
-        table.set_homogeneous(True)
-
-        box1 = gtk.VBox()
-        table.attach(box1, 0, 1, 0, 1)
-        
-        box2 = gtk.VBox()
-        table.attach(box2, 1, 2, 0, 1)
-        
-        flightFrame = self._buildFlightFrame()
-        box1.pack_start(flightFrame, False, False, 4)
-
-        routeFrame = self._buildRouteFrame()
-        box1.pack_start(routeFrame, False, False, 4)
-
-        departureFrame = self._buildDepartureFrame()
-        box2.pack_start(departureFrame, True, True, 4)
-
-        arrivalFrame = self._buildArrivalFrame()
-        box2.pack_start(arrivalFrame, True, True, 4)
-
-        statisticsFrame = self._buildStatisticsFrame()
-        box2.pack_start(statisticsFrame, False, False, 4)
-
-        miscellaneousFrame = self._buildMiscellaneousFrame()
-        box1.pack_start(miscellaneousFrame, False, False, 4)
-
-        return table
-
-    def _buildFlightFrame(self):
-        """Build the frame for the flight data."""
-        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_flight"))
-        
-        dataBox = gtk.HBox()
-        mainBox.pack_start(dataBox, False, False, 0)
-        
-        self._callsign = \
-            PIREPViewer.addLabeledData(dataBox,
-                                       xstr("pirepView_callsign"),
-                                       width = 8)
-
-        self._tailNumber = \
-            PIREPViewer.addLabeledData(dataBox,
-                                       xstr("pirepView_tailNumber"),
-                                       width = 7)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        dataBox = gtk.HBox()
-        mainBox.pack_start(dataBox, False, False, 0)
-
-        self._aircraftType = \
-            PIREPViewer.addLabeledData(dataBox,
-                                       xstr("pirepView_aircraftType"),
-                                       width = 25)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        table = gtk.Table(3, 2)
-        mainBox.pack_start(table, False, False, 0)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)        
-
-        self._departureICAO = \
-            PIREPViewer.tableAttach(table, 0, 0,
-                                    xstr("pirepView_departure"),
-                                    width = 5)
-
-        self._departureTime = \
-            PIREPViewer.tableAttach(table, 1, 0,
-                                    xstr("pirepView_departure_time"),
-                                    width = 6)
-
-        self._arrivalICAO = \
-            PIREPViewer.tableAttach(table, 0, 1,
-                                    xstr("pirepView_arrival"),
-                                    width = 5)
-
-        self._arrivalTime = \
-            PIREPViewer.tableAttach(table, 1, 1,
-                                    xstr("pirepView_arrival_time"),
-                                    width = 6)
-
-        table = gtk.Table(3, 2)
-        mainBox.pack_start(table, False, False, 0)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)        
-
-        self._numPassengers = \
-            PIREPViewer.tableAttach(table, 0, 0,
-                                    xstr("pirepView_numPassengers"),
-                                    width = 4)
-
-        self._numCrew = \
-            PIREPViewer.tableAttach(table, 1, 0,
-                                    xstr("pirepView_numCrew"),
-                                    width = 3)
-
-        self._bagWeight = \
-            PIREPViewer.tableAttach(table, 0, 1, 
-                                    xstr("pirepView_bagWeight"),
-                                    width = 5)
-
-        self._cargoWeight = \
-            PIREPViewer.tableAttach(table, 1, 1, 
-                                    xstr("pirepView_cargoWeight"),
-                                    width = 5)
-
-        self._mailWeight = \
-            PIREPViewer.tableAttach(table, 2, 1, 
-                                    xstr("pirepView_mailWeight"),
-                                    width = 5)
-            
-        PIREPViewer.addVFiller(mainBox)
-
-        mainBox.pack_start(PIREPViewer.getLabel(xstr("pirepView_route")),
-                           False, False, 0)
-        
-        (routeWindow, self._route) = PIREPViewer.getTextWindow()
-        mainBox.pack_start(routeWindow, False, False, 0)
-
-        return frame
-
-    def _buildRouteFrame(self):
-        """Build the frame for the user-specified route and flight
-        level."""
-        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_route"))
-
-        levelBox = gtk.HBox()
-        mainBox.pack_start(levelBox, False, False, 0)
-        
-        self._filedCruiseLevel = \
-            PIREPViewer.addLabeledData(levelBox, 
-                                       xstr("pirepView_filedCruiseLevel"),
-                                       width = 6)
-
-        self._modifiedCruiseLevel = \
-            PIREPViewer.addLabeledData(levelBox,
-                                       xstr("pirepView_modifiedCruiseLevel"),
-                                       width = 6)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        (routeWindow, self._userRoute) = PIREPViewer.getTextWindow()
-        mainBox.pack_start(routeWindow, False, False, 0)
-
-        return frame
-
-    def _buildDepartureFrame(self):
-        """Build the frame for the departure data."""        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_departure"))
-
-        mainBox.pack_start(PIREPViewer.getLabel("METAR:"),
-                           False, False, 0)
-        (metarWindow, self._departureMETAR) = \
-            PIREPViewer.getTextWindow(heightRequest = -1)
-        mainBox.pack_start(metarWindow, True, True, 0)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        dataBox = gtk.HBox()
-        mainBox.pack_start(dataBox, False, False, 0)
-        
-        self._departureRunway = \
-            PIREPViewer.addLabeledData(dataBox,
-                                       xstr("pirepView_runway"),
-                                       width = 5)
-
-        self._sid = \
-            PIREPViewer.addLabeledData(dataBox,
-                                       xstr("pirepView_sid"),
-                                       width = 12)
-
-        return frame
-                           
-    def _buildArrivalFrame(self):
-        """Build the frame for the arrival data."""        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_arrival"))
-
-        mainBox.pack_start(PIREPViewer.getLabel("METAR:"),
-                           False, False, 0)
-        (metarWindow, self._arrivalMETAR) = \
-            PIREPViewer.getTextWindow(heightRequest = -1)
-        mainBox.pack_start(metarWindow, True, True, 0)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        table = gtk.Table(2, 2)
-        mainBox.pack_start(table, False, False, 0)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)        
-
-        self._star = \
-            PIREPViewer.tableAttach(table, 0, 0,
-                                    xstr("pirepView_star"),
-                                    width = 12)
-
-        self._transition = \
-            PIREPViewer.tableAttach(table, 1, 0,
-                                    xstr("pirepView_transition"),
-                                    width = 12)
-                           
-        self._approachType = \
-            PIREPViewer.tableAttach(table, 0, 1,
-                                    xstr("pirepView_approachType"),
-                                    width = 7)
-                           
-        self._arrivalRunway = \
-            PIREPViewer.tableAttach(table, 1, 1, 
-                                    xstr("pirepView_runway"),
-                                    width = 5)
-                           
-        return frame
-
-    def _buildStatisticsFrame(self):
-        """Build the frame for the statistics data."""        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_statistics"))
-
-        table = gtk.Table(4, 2)
-        mainBox.pack_start(table, False, False, 0)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)
-        table.set_homogeneous(False)
-        
-        self._blockTimeStart = \
-            PIREPViewer.tableAttach(table, 0, 0,
-                                    xstr("pirepView_blockTimeStart"),
-                                    width = 6)
-        
-        self._blockTimeEnd = \
-            PIREPViewer.tableAttach(table, 1, 0,
-                                    xstr("pirepView_blockTimeEnd"),
-                                    width = 8)
-
-        self._flightTimeStart = \
-            PIREPViewer.tableAttach(table, 0, 1,
-                                    xstr("pirepView_flightTimeStart"),
-                                    width = 6)
-        
-        self._flightTimeEnd = \
-            PIREPViewer.tableAttach(table, 1, 1, 
-                                    xstr("pirepView_flightTimeEnd"),
-                                    width = 6)
-
-        self._flownDistance = \
-            PIREPViewer.tableAttach(table, 0, 2,
-                                    xstr("pirepView_flownDistance"),
-                                    width = 8)
-        
-        self._fuelUsed = \
-            PIREPViewer.tableAttach(table, 1, 2,
-                                    xstr("pirepView_fuelUsed"),
-                                    width = 6)
-        
-        self._rating = \
-            PIREPViewer.tableAttach(table, 0, 3,
-                                    xstr("pirepView_rating"),
-                                    width = 7)
-        return frame
-
-    def _buildMiscellaneousFrame(self):
-        """Build the frame for the miscellaneous data."""        
-        (frame, mainBox) = PIREPViewer.createFrame(xstr("pirepView_frame_miscellaneous"))
-
-        table = gtk.Table(3, 2)
-        mainBox.pack_start(table, False, False, 0)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)        
-        
-        self._flownNumPassengers = \
-            PIREPViewer.tableAttach(table, 0, 0,
-                                    xstr("pirepView_numPassengers"),
-                                    width = 4)
-
-        self._flownNumCrew = \
-            PIREPViewer.tableAttach(table, 1, 0,
-                                    xstr("pirepView_numCrew"),
-                                    width = 3)
-
-        self._flownBagWeight = \
-            PIREPViewer.tableAttach(table, 0, 1,
-                                    xstr("pirepView_bagWeight"),
-                                    width = 5)
-
-        self._flownCargoWeight = \
-            PIREPViewer.tableAttach(table, 1, 1,
-                                    xstr("pirepView_cargoWeight"),
-                                    width = 6)
-
-        self._flownMailWeight = \
-            PIREPViewer.tableAttach(table, 2, 1,
-                                    xstr("pirepView_mailWeight"),
-                                    width = 5)
-
-        self._flightType = \
-            PIREPViewer.tableAttach(table, 0, 2,
-                                    xstr("pirepView_flightType"),
-                                    width = 15)
-            
-        self._online = \
-            PIREPViewer.tableAttach(table, 1, 2,
-                                    xstr("pirepView_online"),
-                                    width = 5)
-
-        PIREPViewer.addVFiller(mainBox)
-
-        mainBox.pack_start(PIREPViewer.getLabel(xstr("pirepView_delayCodes")),
-                           False, False, 0)
-    
-        (textWindow, self._delayCodes) = PIREPViewer.getTextWindow()
-        mainBox.pack_start(textWindow, False, False, 0)
-
-        return frame        
-
-    def _buildCommentsTab(self):
-        """Build the tab with the comments and flight defects."""
-        table = gtk.Table(2, 1)
-        table.set_col_spacings(16)
-
-        (frame, commentsBox) = \
-            PIREPViewer.createFrame(xstr("pirepView_comments"))
-        table.attach(frame, 0, 1, 0, 1)
-
-        (commentsWindow, self._comments) = \
-            PIREPViewer.getTextWindow(heightRequest = -1)
-        commentsBox.pack_start(commentsWindow, True, True, 0)
-
-        (frame, flightDefectsBox) = \
-            PIREPViewer.createFrame(xstr("pirepView_flightDefects"))
-        table.attach(frame, 1, 2, 0, 1)
-
-        (flightDefectsWindow, self._flightDefects) = \
-            PIREPViewer.getTextWindow(heightRequest = -1)
-        flightDefectsBox.pack_start(flightDefectsWindow, True, True, 0)
-        
-        return table
-
-    def _buildLogTab(self):
-        """Build the log tab."""
-        mainBox = gtk.VBox()
-
-        (logWindow, self._log) = PIREPViewer.getTextWindow(heightRequest = -1)
-        addFaultTag(self._log.get_buffer())
-        mainBox.pack_start(logWindow, True, True, 0)
-        
-        return mainBox
-        
-#------------------------------------------------------------------------------
Index: src/mlx/gui/prefs.py
===================================================================
--- src/mlx/gui/prefs.py	(revision 684)
+++ src/mlx/gui/prefs.py	(revision 684)
@@ -0,0 +1,846 @@
+
+from common import *
+
+from mlx.i18n import xstr
+import mlx.const as const
+import mlx.config as config
+
+import urlparse
+
+#------------------------------------------------------------------------------
+
+## @package mlx.gui.prefs
+#
+# The preferences dialog.
+#
+# This module implements the preferences dialog, allowing the editing of the
+# configuration of the program. The preferences are grouped into tabs, each
+# containing the necessary controls to set the various options.
+
+#------------------------------------------------------------------------------
+
+class Hotkey(gtk.HBox):
+    """A widget to handle a hotkey."""
+
+    # Constant to denote that the status of the Ctrl modifier is changed
+    CHANGED_CTRL = 1
+
+    # Constant to denote that the status of the Shift modifier is changed
+    CHANGED_SHIFT = 2
+
+    # Constant to denote that the value of the key is changed
+    CHANGED_KEY = 3
+
+    def __init__(self, labelText, tooltips):
+        """Construct the hotkey widget.
+
+        labelText is the text for the label before the hotkey.
+
+        The tooltips parameter is an array of the tooltips for:
+        - the hotkey combo box,
+        - the control check box, and
+        - the shift check box."""
+        super(Hotkey, self).__init__()
+
+        label = gtk.Label(labelText)
+        label.set_use_underline(True)
+        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                       xscale = 0.0, yscale = 0.0)
+        labelAlignment.set_padding(padding_top = 0, padding_bottom = 0,
+                                   padding_left = 0, padding_right = 4)
+        labelAlignment.add(label)
+        self.pack_start(labelAlignment, False, False, 0)
+
+        self._ctrl = gtk.CheckButton("Ctrl")
+        self._ctrl.set_tooltip_text(tooltips[1])
+        self._ctrl.connect("toggled", self._ctrlToggled)
+        self.pack_start(self._ctrl, False, False, 4)
+
+        self._shift = gtk.CheckButton("Shift")
+        self._shift.set_tooltip_text(tooltips[2])
+        self._shift.connect("toggled", self._shiftToggled)
+        self.pack_start(self._shift, False, False, 4)
+
+        self._hotkeyModel = gtk.ListStore(str)
+        for keyCode in range(ord("0"), ord("9")+1) + range(ord("A"), ord("Z")+1):
+            self._hotkeyModel.append([chr(keyCode)])
+
+        self._hotkey = gtk.ComboBox(model = self._hotkeyModel)
+        cell = gtk.CellRendererText()
+        self._hotkey.pack_start(cell, True)
+        self._hotkey.add_attribute(cell, 'text', 0)
+        self._hotkey.set_tooltip_text(tooltips[0])
+        self._hotkey.connect("changed", self._keyChanged)
+        self.pack_start(self._hotkey, False, False, 4)
+
+        label.set_mnemonic_widget(self._hotkey)
+
+        self._setting = False
+
+    @property
+    def ctrl(self):
+        """Get whether the Ctrl modifier is selected."""
+        return self._ctrl.get_active()
+
+    @ctrl.setter
+    def ctrl(self, ctrl):
+        """Get whether the Ctrl modifier is selected."""
+        self._setting = True
+        self._ctrl.set_active(ctrl)
+        self._setting = False
+
+    @property
+    def shift(self):
+        """Get whether the Shift modifier is selected."""
+        return self._shift.get_active()
+
+    @shift.setter
+    def shift(self, shift):
+        """Get whether the Shift modifier is selected."""
+        self._setting = True
+        self._shift.set_active(shift)
+        self._setting = False
+
+    @property
+    def key(self):
+        """Get the value of the key."""
+        return self._hotkeyModel.get_value(self._hotkey.get_active_iter(), 0)
+
+    @key.setter
+    def key(self, key):
+        """Set the value of the key."""
+        self._setting = True
+
+        hotkeyModel = self._hotkeyModel
+        iter = hotkeyModel.get_iter_first()
+        while iter is not None and \
+              hotkeyModel.get_value(iter, 0)!=key:
+            iter = hotkeyModel.iter_next(iter)
+
+        if iter is None:
+            iter = hotkeyModel.get_iter_first()
+
+        self._hotkey.set_active_iter(iter)
+
+        self._setting = False
+
+    def set(self, hotkey):
+        """Set the hotkey widget from the given hotkey."""
+        self.ctrl = hotkey.ctrl
+        self.shift = hotkey.shift
+        self.key = hotkey.key
+
+    def get(self):
+        """Get a hotkey corresponding to the settings in the widghet."""
+
+        key = self._hotkeyModel.get_value(self._hotkey.get_active_iter(), 0)
+
+        return config.Hotkey(ctrl = self.ctrl, shift = self.shift,
+                             key = self.key)
+
+    def _ctrlToggled(self, checkButton):
+        """Called when the status of the Ctrl modifier has changed."""
+        if not self._setting:
+            self.emit("hotkey-changed", Hotkey.CHANGED_CTRL)
+
+    def _shiftToggled(self, checkButton):
+        """Called when the status of the Shift modifier has changed."""
+        if not self._setting:
+            self.emit("hotkey-changed", Hotkey.CHANGED_SHIFT)
+
+    def _keyChanged(self, comboBox):
+        """Called when the value of the key has changed."""
+        if not self._setting:
+            self.emit("hotkey-changed", Hotkey.CHANGED_KEY)
+
+    def __eq__(self, other):
+        """Determine if the two hotkeys are equal."""
+        return self.ctrl==other.ctrl and self.shift==other.shift and \
+               self.key==other.key
+
+#------------------------------------------------------------------------------
+
+gobject.signal_new("hotkey-changed", Hotkey, gobject.SIGNAL_RUN_FIRST,
+                   None, (int,))
+
+#------------------------------------------------------------------------------
+
+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.set_resizable(False)
+
+        self._gui = gui
+        self._settingFromConfig = False
+
+        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)
+
+        sounds = self._buildSounds()
+        label = gtk.Label(xstr("prefs_tab_sounds"))
+        label.set_use_underline(True)
+        label.set_tooltip_text(xstr("prefs_tab_sounds_tooltip"))
+        notebook.append_page(sounds, 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._settingFromConfig = True
+
+        self._setLanguage(config.language)
+        self._hideMinimizedWindow.set_active(config.hideMinimizedWindow)
+        self._quitOnClose.set_active(config.quitOnClose)
+        self._onlineGateSystem.set_active(config.onlineGateSystem)
+        self._onlineACARS.set_active(config.onlineACARS)
+        self._flareTimeFromFS.set_active(config.flareTimeFromFS)
+        self._syncFSTime.set_active(config.syncFSTime)
+        self._usingFS2Crew.set_active(config.usingFS2Crew)
+
+        self._setSmoothing(self._iasSmoothingEnabled, self._iasSmoothingLength,
+                           config.iasSmoothingLength)
+        self._setSmoothing(self._vsSmoothingEnabled, self._vsSmoothingLength,
+                           config.vsSmoothingLength)
+
+        self._useSimBrief.set_active(config.useSimBrief)
+
+        pirepDirectory = config.pirepDirectory
+        self._pirepDirectory.set_text("" if pirepDirectory is None
+                                      else pirepDirectory)
+        self._pirepAutoSave.set_active(config.pirepAutoSave)
+        if not pirepDirectory:
+            self._pirepAutoSave.set_sensitive(False)
+
+        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._enableSounds.set_active(config.enableSounds)
+        self._pilotControlsSounds.set_active(config.pilotControlsSounds)
+        self._pilotHotkey.set(config.pilotHotkey)
+        self._enableApproachCallouts.set_active(config.enableApproachCallouts)
+        self._speedbrakeAtTD.set_active(config.speedbrakeAtTD)
+
+        self._enableChecklists.set_active(config.enableChecklists)
+        self._checklistHotkey.set(config.checklistHotkey)
+
+        self._autoUpdate.set_active(config.autoUpdate)
+        if not config.autoUpdate:
+            self._warnedAutoUpdate = True
+
+        self._updateURL.set_text(config.updateURL)
+
+        self._settingFromConfig = False
+
+    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.quitOnClose = self._quitOnClose.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.usingFS2Crew = self._usingFS2Crew.get_active()
+        config.iasSmoothingLength = self._getSmoothing(self._iasSmoothingEnabled,
+                                                       self._iasSmoothingLength)
+        config.vsSmoothingLength = self._getSmoothing(self._vsSmoothingEnabled,
+                                                       self._vsSmoothingLength)
+        config.useSimBrief = self._useSimBrief.get_active()
+        config.pirepDirectory = text2unicode(self._pirepDirectory.get_text())
+        config.pirepAutoSave = self._pirepAutoSave.get_active()
+
+        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.enableSounds = self._enableSounds.get_active()
+        config.pilotControlsSounds = self._pilotControlsSounds.get_active()
+        config.pilotHotkey = self._pilotHotkey.get()
+        config.enableApproachCallouts = self._enableApproachCallouts.get_active()
+        config.speedbrakeAtTD = self._speedbrakeAtTD.get_active()
+
+        config.enableChecklists = self._enableChecklists.get_active()
+        config.checklistHotkey = self._checklistHotkey.get()
+
+        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 = 0, padding_bottom = 8,
+                                  padding_left = 4, padding_right = 4)
+        mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        guiBox = self._createFrame(mainBox, xstr("prefs_frame_gui"))
+
+        languageBox = gtk.HBox()
+        guiBox.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"))
+        guiBox.pack_start(self._hideMinimizedWindow, False, False, 4)
+
+        self._quitOnClose = gtk.CheckButton(xstr("prefs_quitOnClose"))
+        self._quitOnClose.set_use_underline(True)
+        self._quitOnClose.set_tooltip_text(xstr("prefs_quitOnClose_tooltip"))
+        guiBox.pack_start(self._quitOnClose, False, False, 4)
+
+        onlineBox = self._createFrame(mainBox, xstr("prefs_frame_online"))
+
+        self._onlineGateSystem = gtk.CheckButton(xstr("prefs_onlineGateSystem"))
+        self._onlineGateSystem.set_use_underline(True)
+        self._onlineGateSystem.set_tooltip_text(xstr("prefs_onlineGateSystem_tooltip"))
+        onlineBox.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"))
+        onlineBox.pack_start(self._onlineACARS, False, False, 4)
+
+        simulatorBox = self._createFrame(mainBox, xstr("prefs_frame_simulator"))
+
+        self._flareTimeFromFS = gtk.CheckButton(xstr("prefs_flaretimeFromFS"))
+        self._flareTimeFromFS.set_use_underline(True)
+        self._flareTimeFromFS.set_tooltip_text(xstr("prefs_flaretimeFromFS_tooltip"))
+        simulatorBox.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"))
+        simulatorBox.pack_start(self._syncFSTime, False, False, 4)
+
+        self._usingFS2Crew = gtk.CheckButton(xstr("prefs_usingFS2Crew"))
+        self._usingFS2Crew.set_use_underline(True)
+        self._usingFS2Crew.set_tooltip_text(xstr("prefs_usingFS2Crew_tooltip"))
+        simulatorBox.pack_start(self._usingFS2Crew, False, False, 4)
+
+        (iasSmoothingBox, self._iasSmoothingEnabled,
+         self._iasSmoothingLength) = \
+           self._createSmoothingBox(xstr("prefs_iasSmoothingEnabled"),
+                                    xstr("prefs_iasSmoothingEnabledTooltip"))
+        simulatorBox.pack_start(iasSmoothingBox, False, False, 4)
+
+        (vsSmoothingBox, self._vsSmoothingEnabled,
+         self._vsSmoothingLength) = \
+           self._createSmoothingBox(xstr("prefs_vsSmoothingEnabled"),
+                                    xstr("prefs_vsSmoothingEnabledTooltip"))
+        simulatorBox.pack_start(vsSmoothingBox, False, False, 4)
+
+        self._useSimBrief = gtk.CheckButton(xstr("prefs_useSimBrief"))
+        self._useSimBrief.set_use_underline(True)
+        self._useSimBrief.set_tooltip_text(xstr("prefs_useSimBrief_tooltip"))
+        mainBox.pack_start(self._useSimBrief, False, False, 0)
+
+        pirepBox = gtk.HBox()
+        mainBox.pack_start(pirepBox, False, False, 8)
+
+        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"))
+        self._pirepDirectory.connect("changed", self._pirepDirectoryChanged)
+        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)
+
+        self._pirepAutoSave = gtk.CheckButton(xstr("prefs_pirepAutoSave"))
+        self._pirepAutoSave.set_use_underline(True)
+        self._pirepAutoSave.set_tooltip_text(xstr("prefs_pirepAutoSave_tooltip"))
+        mainBox.pack_start(self._pirepAutoSave, False, False, 0)
+
+        return mainAlignment
+
+    def _createFrame(self, mainBox, label):
+        """Create a frame with an inner alignment and VBox.
+
+        Return the vbox."""
+        frame = gtk.Frame(label = label)
+        mainBox.pack_start(frame, False, False, 4)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                  xscale = 1.0, yscale = 0.0)
+        alignment.set_padding(padding_top = 4, padding_bottom = 0,
+                              padding_left = 0, padding_right = 0)
+        frame.add(alignment)
+        vbox = gtk.VBox()
+        alignment.add(vbox)
+
+        return vbox
+
+    def _createSmoothingBox(self, checkBoxLabel, checkBoxTooltip,
+                            maxSeconds = 10):
+        """Create a HBox that contains entry fields for smoothing some value."""
+        smoothingBox = gtk.HBox()
+
+        smoothingEnabled = gtk.CheckButton(checkBoxLabel)
+        smoothingEnabled.set_use_underline(True)
+        smoothingEnabled.set_tooltip_text(checkBoxTooltip)
+
+        smoothingBox.pack_start(smoothingEnabled, False, False, 0)
+
+        smoothingLength = gtk.SpinButton()
+        smoothingLength.set_numeric(True)
+        smoothingLength.set_range(2, maxSeconds)
+        smoothingLength.set_increments(1, 1)
+        smoothingLength.set_alignment(1.0)
+        smoothingLength.set_width_chars(2)
+
+        smoothingBox.pack_start(smoothingLength, False, False, 0)
+
+        smoothingBox.pack_start(gtk.Label(xstr("prefs_smoothing_seconds")),
+                                False, False, 4)
+
+        smoothingEnabled.connect("toggled", self._smoothingToggled,
+                                 smoothingLength)
+        smoothingLength.set_sensitive(False)
+
+        return (smoothingBox, smoothingEnabled, smoothingLength)
+
+    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 _smoothingToggled(self, smoothingEnabled, smoothingLength):
+        """Called when a smoothing enabled check box is toggled."""
+        sensitive = smoothingEnabled.get_active()
+        smoothingLength.set_sensitive(sensitive)
+        if sensitive:
+            smoothingLength.grab_focus()
+
+    def _setSmoothing(self, smoothingEnabled, smoothingLength, smoothing):
+        """Set the smoothing controls from the given value.
+
+        If the value is less than 2, smoothing is disabled. The smoothing
+        length is the absolute value of the value."""
+        smoothingEnabled.set_active(smoothing>=2)
+        smoothingLength.set_value(abs(smoothing))
+
+    def _getSmoothing(self, smoothingEnabled, smoothingLength):
+        """Get the smoothing value from the given controls.
+
+        The returned value is the value of smoothingLength multiplied by -1, if
+        smoothing is disabled."""
+        value = smoothingLength.get_value_as_int()
+        if not smoothingEnabled.get_active():
+            value *= -1
+        return value
+
+    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(text2unicode(dialog.get_filename()))
+
+    def _pirepDirectoryChanged(self, entry):
+        """Called when the PIREP directory is changed."""
+        if self._pirepDirectory.get_text():
+            self._pirepAutoSave.set_sensitive(True)
+        else:
+            self._pirepAutoSave.set_active(False)
+            self._pirepAutoSave.set_sensitive(False)
+
+    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 _buildSounds(self):
+        """Build the page for the sounds."""
+        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                      xscale = 1.0, yscale = 1.0)
+        mainAlignment.set_padding(padding_top = 8, padding_bottom = 8,
+                                  padding_left = 4, padding_right = 4)
+
+        mainBox = gtk.VBox()
+        mainAlignment.add(mainBox)
+
+        backgroundFrame = gtk.Frame(label = xstr("prefs_sounds_frame_bg"))
+        mainBox.pack_start(backgroundFrame, False, False, 4)
+
+        backgroundAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                            xscale = 1.0, yscale = 0.0)
+        backgroundAlignment.set_padding(padding_top = 4, padding_bottom = 4,
+                                        padding_left = 4, padding_right = 4)
+        backgroundFrame.add(backgroundAlignment)
+
+        backgroundBox = gtk.VBox()
+        backgroundAlignment.add(backgroundBox)
+
+        self._enableSounds = gtk.CheckButton(xstr("prefs_sounds_enable"))
+        self._enableSounds.set_use_underline(True)
+        self._enableSounds.set_tooltip_text(xstr("prefs_sounds_enable_tooltip"))
+        self._enableSounds.connect("toggled", self._enableSoundsToggled)
+        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
+                                  xscale = 1.0, yscale = 0.0)
+        alignment.add(self._enableSounds)
+        backgroundBox.pack_start(alignment, False, False, 4)
+
+        self._pilotControlsSounds = gtk.CheckButton(xstr("prefs_sounds_pilotControls"))
+        self._pilotControlsSounds.set_use_underline(True)
+        self._pilotControlsSounds.set_tooltip_text(xstr("prefs_sounds_pilotControls_tooltip"))
+        self._pilotControlsSounds.connect("toggled", self._pilotControlsSoundsToggled)
+        backgroundBox.pack_start(self._pilotControlsSounds, False, False, 4)
+
+        self._pilotHotkey = Hotkey(xstr("prefs_sounds_pilotHotkey"),
+                                   [xstr("prefs_sounds_pilotHotkey_tooltip"),
+                                    xstr("prefs_sounds_pilotHotkeyCtrl_tooltip"),
+                                    xstr("prefs_sounds_pilotHotkeyShift_tooltip")])
+
+        backgroundBox.pack_start(self._pilotHotkey, False, False, 4)
+
+        self._enableApproachCallouts = gtk.CheckButton(xstr("prefs_sounds_approachCallouts"))
+        self._enableApproachCallouts.set_use_underline(True)
+        self._enableApproachCallouts.set_tooltip_text(xstr("prefs_sounds_approachCallouts_tooltip"))
+        backgroundBox.pack_start(self._enableApproachCallouts, False, False, 4)
+
+        self._speedbrakeAtTD = gtk.CheckButton(xstr("prefs_sounds_speedbrakeAtTD"))
+        self._speedbrakeAtTD.set_use_underline(True)
+        self._speedbrakeAtTD.set_tooltip_text(xstr("prefs_sounds_speedbrakeAtTD_tooltip"))
+        backgroundBox.pack_start(self._speedbrakeAtTD, False, False, 4)
+
+        checklistFrame = gtk.Frame(label = xstr("prefs_sounds_frame_checklists"))
+        mainBox.pack_start(checklistFrame, False, False, 4)
+
+        checklistAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
+                                           xscale = 1.0, yscale = 0.0)
+        checklistAlignment.set_padding(padding_top = 4, padding_bottom = 4,
+                                       padding_left = 4, padding_right = 4)
+        checklistFrame.add(checklistAlignment)
+
+        checklistBox = gtk.VBox()
+        checklistAlignment.add(checklistBox)
+
+        self._enableChecklists = gtk.CheckButton(xstr("prefs_sounds_enableChecklists"))
+        self._enableChecklists.set_use_underline(True)
+        self._enableChecklists.set_tooltip_text(xstr("prefs_sounds_enableChecklists_tooltip"))
+        self._enableChecklists.connect("toggled", self._enableChecklistsToggled)
+        checklistBox.pack_start(self._enableChecklists, False, False, 4)
+
+        self._checklistHotkey = Hotkey(xstr("prefs_sounds_checklistHotkey"),
+                                       [xstr("prefs_sounds_checklistHotkey_tooltip"),
+                                        xstr("prefs_sounds_checklistHotkeyCtrl_tooltip"),
+                                        xstr("prefs_sounds_checklistHotkeyShift_tooltip")])
+
+        checklistBox.pack_start(self._checklistHotkey, False, False, 4)
+
+        self._enableSoundsToggled(self._enableSounds)
+        self._enableChecklistsToggled(self._enableChecklists)
+
+        self._pilotHotkey.connect("hotkey-changed", self._reconcileHotkeys,
+                                  self._checklistHotkey)
+        self._checklistHotkey.connect("hotkey-changed", self._reconcileHotkeys,
+                                      self._pilotHotkey)
+
+        return mainAlignment
+
+    def _enableSoundsToggled(self, button):
+        """Called when the enable sounds button is toggled."""
+        active = button.get_active()
+        self._pilotControlsSounds.set_sensitive(active)
+        self._pilotControlsSoundsToggled(self._pilotControlsSounds)
+        self._enableApproachCallouts.set_sensitive(active)
+        self._speedbrakeAtTD.set_sensitive(active)
+
+    def _pilotControlsSoundsToggled(self, button):
+        """Called when the enable sounds button is toggled."""
+        active = button.get_active() and self._enableSounds.get_active()
+        self._pilotHotkey.set_sensitive(active)
+        if active and self._checklistHotkey.get_sensitive():
+            self._reconcileHotkeys(self._checklistHotkey, Hotkey.CHANGED_SHIFT,
+                                   self._pilotHotkey)
+
+    def _enableChecklistsToggled(self, button):
+        """Called when the enable checklists button is toggled."""
+        active = button.get_active()
+        self._checklistHotkey.set_sensitive(active)
+        if active and self._pilotHotkey.get_sensitive():
+            self._reconcileHotkeys(self._pilotHotkey, Hotkey.CHANGED_SHIFT,
+                                   self._checklistHotkey)
+
+    def _reconcileHotkeys(self, changedHotkey, what, otherHotkey):
+        """Reconcile the given hotkeys so that they are different.
+
+        changedHotkey is the hotkey that has changed. what is one of the
+        Hotkey.CHANGED_XXX constants denoting what has changed. otherHotkey is
+        the other hotkey that must be reconciled.
+
+        If the other hotkey is not sensitive or is not equal to the changed
+        one, nothing happens.
+
+        Otherwise, if the status of the Ctrl modifier has changed, the status
+        of the Ctrl modifier on the other hotkey will be negated. Similarly, if
+        the Shift modifier has changed. If the key has changed, the Shift
+        modifier is negated in the other hotkey."""
+        if otherHotkey.get_sensitive() and changedHotkey==otherHotkey:
+            if what==Hotkey.CHANGED_CTRL:
+                otherHotkey.ctrl = not changedHotkey.ctrl
+            elif what==Hotkey.CHANGED_SHIFT or what==Hotkey.CHANGED_KEY:
+                otherHotkey.shift = not changedHotkey.shift
+
+    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
+
+        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._settingFromConfig 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 684)
+++ 	(revision )
@@ -1,846 +1,0 @@
-
-from common import *
-
-from mlx.i18n import xstr
-import mlx.const as const
-import mlx.config as config
-
-import urlparse
-
-#------------------------------------------------------------------------------
-
-## @package mlx.gui.prefs
-#
-# The preferences dialog.
-#
-# This module implements the preferences dialog, allowing the editing of the
-# configuration of the program. The preferences are grouped into tabs, each
-# containing the necessary controls to set the various options.
-
-#------------------------------------------------------------------------------
-
-class Hotkey(gtk.HBox):
-    """A widget to handle a hotkey."""
-
-    # Constant to denote that the status of the Ctrl modifier is changed
-    CHANGED_CTRL = 1
-
-    # Constant to denote that the status of the Shift modifier is changed
-    CHANGED_SHIFT = 2
-
-    # Constant to denote that the value of the key is changed
-    CHANGED_KEY = 3
-
-    def __init__(self, labelText, tooltips):
-        """Construct the hotkey widget.
-
-        labelText is the text for the label before the hotkey.
-
-        The tooltips parameter is an array of the tooltips for:
-        - the hotkey combo box,
-        - the control check box, and
-        - the shift check box."""
-        super(Hotkey, self).__init__()
-
-        label = gtk.Label(labelText)
-        label.set_use_underline(True)
-        labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                       xscale = 0.0, yscale = 0.0)
-        labelAlignment.set_padding(padding_top = 0, padding_bottom = 0,
-                                   padding_left = 0, padding_right = 4)
-        labelAlignment.add(label)
-        self.pack_start(labelAlignment, False, False, 0)
-
-        self._ctrl = gtk.CheckButton("Ctrl")
-        self._ctrl.set_tooltip_text(tooltips[1])
-        self._ctrl.connect("toggled", self._ctrlToggled)
-        self.pack_start(self._ctrl, False, False, 4)
-
-        self._shift = gtk.CheckButton("Shift")
-        self._shift.set_tooltip_text(tooltips[2])
-        self._shift.connect("toggled", self._shiftToggled)
-        self.pack_start(self._shift, False, False, 4)
-
-        self._hotkeyModel = gtk.ListStore(str)
-        for keyCode in range(ord("0"), ord("9")+1) + range(ord("A"), ord("Z")+1):
-            self._hotkeyModel.append([chr(keyCode)])
-
-        self._hotkey = gtk.ComboBox(model = self._hotkeyModel)
-        cell = gtk.CellRendererText()
-        self._hotkey.pack_start(cell, True)
-        self._hotkey.add_attribute(cell, 'text', 0)
-        self._hotkey.set_tooltip_text(tooltips[0])
-        self._hotkey.connect("changed", self._keyChanged)
-        self.pack_start(self._hotkey, False, False, 4)
-
-        label.set_mnemonic_widget(self._hotkey)
-
-        self._setting = False
-
-    @property
-    def ctrl(self):
-        """Get whether the Ctrl modifier is selected."""
-        return self._ctrl.get_active()
-
-    @ctrl.setter
-    def ctrl(self, ctrl):
-        """Get whether the Ctrl modifier is selected."""
-        self._setting = True
-        self._ctrl.set_active(ctrl)
-        self._setting = False
-
-    @property
-    def shift(self):
-        """Get whether the Shift modifier is selected."""
-        return self._shift.get_active()
-
-    @shift.setter
-    def shift(self, shift):
-        """Get whether the Shift modifier is selected."""
-        self._setting = True
-        self._shift.set_active(shift)
-        self._setting = False
-
-    @property
-    def key(self):
-        """Get the value of the key."""
-        return self._hotkeyModel.get_value(self._hotkey.get_active_iter(), 0)
-
-    @key.setter
-    def key(self, key):
-        """Set the value of the key."""
-        self._setting = True
-
-        hotkeyModel = self._hotkeyModel
-        iter = hotkeyModel.get_iter_first()
-        while iter is not None and \
-              hotkeyModel.get_value(iter, 0)!=key:
-            iter = hotkeyModel.iter_next(iter)
-
-        if iter is None:
-            iter = hotkeyModel.get_iter_first()
-
-        self._hotkey.set_active_iter(iter)
-
-        self._setting = False
-
-    def set(self, hotkey):
-        """Set the hotkey widget from the given hotkey."""
-        self.ctrl = hotkey.ctrl
-        self.shift = hotkey.shift
-        self.key = hotkey.key
-
-    def get(self):
-        """Get a hotkey corresponding to the settings in the widghet."""
-
-        key = self._hotkeyModel.get_value(self._hotkey.get_active_iter(), 0)
-
-        return config.Hotkey(ctrl = self.ctrl, shift = self.shift,
-                             key = self.key)
-
-    def _ctrlToggled(self, checkButton):
-        """Called when the status of the Ctrl modifier has changed."""
-        if not self._setting:
-            self.emit("hotkey-changed", Hotkey.CHANGED_CTRL)
-
-    def _shiftToggled(self, checkButton):
-        """Called when the status of the Shift modifier has changed."""
-        if not self._setting:
-            self.emit("hotkey-changed", Hotkey.CHANGED_SHIFT)
-
-    def _keyChanged(self, comboBox):
-        """Called when the value of the key has changed."""
-        if not self._setting:
-            self.emit("hotkey-changed", Hotkey.CHANGED_KEY)
-
-    def __eq__(self, other):
-        """Determine if the two hotkeys are equal."""
-        return self.ctrl==other.ctrl and self.shift==other.shift and \
-               self.key==other.key
-
-#------------------------------------------------------------------------------
-
-gobject.signal_new("hotkey-changed", Hotkey, gobject.SIGNAL_RUN_FIRST,
-                   None, (int,))
-
-#------------------------------------------------------------------------------
-
-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.set_resizable(False)
-
-        self._gui = gui
-        self._settingFromConfig = False
-
-        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)
-
-        sounds = self._buildSounds()
-        label = gtk.Label(xstr("prefs_tab_sounds"))
-        label.set_use_underline(True)
-        label.set_tooltip_text(xstr("prefs_tab_sounds_tooltip"))
-        notebook.append_page(sounds, 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._settingFromConfig = True
-
-        self._setLanguage(config.language)
-        self._hideMinimizedWindow.set_active(config.hideMinimizedWindow)
-        self._quitOnClose.set_active(config.quitOnClose)
-        self._onlineGateSystem.set_active(config.onlineGateSystem)
-        self._onlineACARS.set_active(config.onlineACARS)
-        self._flareTimeFromFS.set_active(config.flareTimeFromFS)
-        self._syncFSTime.set_active(config.syncFSTime)
-        self._usingFS2Crew.set_active(config.usingFS2Crew)
-
-        self._setSmoothing(self._iasSmoothingEnabled, self._iasSmoothingLength,
-                           config.iasSmoothingLength)
-        self._setSmoothing(self._vsSmoothingEnabled, self._vsSmoothingLength,
-                           config.vsSmoothingLength)
-
-        self._useSimBrief.set_active(config.useSimBrief)
-
-        pirepDirectory = config.pirepDirectory
-        self._pirepDirectory.set_text("" if pirepDirectory is None
-                                      else pirepDirectory)
-        self._pirepAutoSave.set_active(config.pirepAutoSave)
-        if not pirepDirectory:
-            self._pirepAutoSave.set_sensitive(False)
-
-        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._enableSounds.set_active(config.enableSounds)
-        self._pilotControlsSounds.set_active(config.pilotControlsSounds)
-        self._pilotHotkey.set(config.pilotHotkey)
-        self._enableApproachCallouts.set_active(config.enableApproachCallouts)
-        self._speedbrakeAtTD.set_active(config.speedbrakeAtTD)
-
-        self._enableChecklists.set_active(config.enableChecklists)
-        self._checklistHotkey.set(config.checklistHotkey)
-
-        self._autoUpdate.set_active(config.autoUpdate)
-        if not config.autoUpdate:
-            self._warnedAutoUpdate = True
-
-        self._updateURL.set_text(config.updateURL)
-
-        self._settingFromConfig = False
-
-    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.quitOnClose = self._quitOnClose.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.usingFS2Crew = self._usingFS2Crew.get_active()
-        config.iasSmoothingLength = self._getSmoothing(self._iasSmoothingEnabled,
-                                                       self._iasSmoothingLength)
-        config.vsSmoothingLength = self._getSmoothing(self._vsSmoothingEnabled,
-                                                       self._vsSmoothingLength)
-        config.useSimBrief = self._useSimBrief.get_active()
-        config.pirepDirectory = text2unicode(self._pirepDirectory.get_text())
-        config.pirepAutoSave = self._pirepAutoSave.get_active()
-
-        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.enableSounds = self._enableSounds.get_active()
-        config.pilotControlsSounds = self._pilotControlsSounds.get_active()
-        config.pilotHotkey = self._pilotHotkey.get()
-        config.enableApproachCallouts = self._enableApproachCallouts.get_active()
-        config.speedbrakeAtTD = self._speedbrakeAtTD.get_active()
-
-        config.enableChecklists = self._enableChecklists.get_active()
-        config.checklistHotkey = self._checklistHotkey.get()
-
-        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 = 0, padding_bottom = 8,
-                                  padding_left = 4, padding_right = 4)
-        mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        guiBox = self._createFrame(mainBox, xstr("prefs_frame_gui"))
-
-        languageBox = gtk.HBox()
-        guiBox.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"))
-        guiBox.pack_start(self._hideMinimizedWindow, False, False, 4)
-
-        self._quitOnClose = gtk.CheckButton(xstr("prefs_quitOnClose"))
-        self._quitOnClose.set_use_underline(True)
-        self._quitOnClose.set_tooltip_text(xstr("prefs_quitOnClose_tooltip"))
-        guiBox.pack_start(self._quitOnClose, False, False, 4)
-
-        onlineBox = self._createFrame(mainBox, xstr("prefs_frame_online"))
-
-        self._onlineGateSystem = gtk.CheckButton(xstr("prefs_onlineGateSystem"))
-        self._onlineGateSystem.set_use_underline(True)
-        self._onlineGateSystem.set_tooltip_text(xstr("prefs_onlineGateSystem_tooltip"))
-        onlineBox.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"))
-        onlineBox.pack_start(self._onlineACARS, False, False, 4)
-
-        simulatorBox = self._createFrame(mainBox, xstr("prefs_frame_simulator"))
-
-        self._flareTimeFromFS = gtk.CheckButton(xstr("prefs_flaretimeFromFS"))
-        self._flareTimeFromFS.set_use_underline(True)
-        self._flareTimeFromFS.set_tooltip_text(xstr("prefs_flaretimeFromFS_tooltip"))
-        simulatorBox.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"))
-        simulatorBox.pack_start(self._syncFSTime, False, False, 4)
-
-        self._usingFS2Crew = gtk.CheckButton(xstr("prefs_usingFS2Crew"))
-        self._usingFS2Crew.set_use_underline(True)
-        self._usingFS2Crew.set_tooltip_text(xstr("prefs_usingFS2Crew_tooltip"))
-        simulatorBox.pack_start(self._usingFS2Crew, False, False, 4)
-
-        (iasSmoothingBox, self._iasSmoothingEnabled,
-         self._iasSmoothingLength) = \
-           self._createSmoothingBox(xstr("prefs_iasSmoothingEnabled"),
-                                    xstr("prefs_iasSmoothingEnabledTooltip"))
-        simulatorBox.pack_start(iasSmoothingBox, False, False, 4)
-
-        (vsSmoothingBox, self._vsSmoothingEnabled,
-         self._vsSmoothingLength) = \
-           self._createSmoothingBox(xstr("prefs_vsSmoothingEnabled"),
-                                    xstr("prefs_vsSmoothingEnabledTooltip"))
-        simulatorBox.pack_start(vsSmoothingBox, False, False, 4)
-
-        self._useSimBrief = gtk.CheckButton(xstr("prefs_useSimBrief"))
-        self._useSimBrief.set_use_underline(True)
-        self._useSimBrief.set_tooltip_text(xstr("prefs_useSimBrief_tooltip"))
-        mainBox.pack_start(self._useSimBrief, False, False, 0)
-
-        pirepBox = gtk.HBox()
-        mainBox.pack_start(pirepBox, False, False, 8)
-
-        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"))
-        self._pirepDirectory.connect("changed", self._pirepDirectoryChanged)
-        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)
-
-        self._pirepAutoSave = gtk.CheckButton(xstr("prefs_pirepAutoSave"))
-        self._pirepAutoSave.set_use_underline(True)
-        self._pirepAutoSave.set_tooltip_text(xstr("prefs_pirepAutoSave_tooltip"))
-        mainBox.pack_start(self._pirepAutoSave, False, False, 0)
-
-        return mainAlignment
-
-    def _createFrame(self, mainBox, label):
-        """Create a frame with an inner alignment and VBox.
-
-        Return the vbox."""
-        frame = gtk.Frame(label = label)
-        mainBox.pack_start(frame, False, False, 4)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                  xscale = 1.0, yscale = 0.0)
-        alignment.set_padding(padding_top = 4, padding_bottom = 0,
-                              padding_left = 0, padding_right = 0)
-        frame.add(alignment)
-        vbox = gtk.VBox()
-        alignment.add(vbox)
-
-        return vbox
-
-    def _createSmoothingBox(self, checkBoxLabel, checkBoxTooltip,
-                            maxSeconds = 10):
-        """Create a HBox that contains entry fields for smoothing some value."""
-        smoothingBox = gtk.HBox()
-
-        smoothingEnabled = gtk.CheckButton(checkBoxLabel)
-        smoothingEnabled.set_use_underline(True)
-        smoothingEnabled.set_tooltip_text(checkBoxTooltip)
-
-        smoothingBox.pack_start(smoothingEnabled, False, False, 0)
-
-        smoothingLength = gtk.SpinButton()
-        smoothingLength.set_numeric(True)
-        smoothingLength.set_range(2, maxSeconds)
-        smoothingLength.set_increments(1, 1)
-        smoothingLength.set_alignment(1.0)
-        smoothingLength.set_width_chars(2)
-
-        smoothingBox.pack_start(smoothingLength, False, False, 0)
-
-        smoothingBox.pack_start(gtk.Label(xstr("prefs_smoothing_seconds")),
-                                False, False, 4)
-
-        smoothingEnabled.connect("toggled", self._smoothingToggled,
-                                 smoothingLength)
-        smoothingLength.set_sensitive(False)
-
-        return (smoothingBox, smoothingEnabled, smoothingLength)
-
-    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 _smoothingToggled(self, smoothingEnabled, smoothingLength):
-        """Called when a smoothing enabled check box is toggled."""
-        sensitive = smoothingEnabled.get_active()
-        smoothingLength.set_sensitive(sensitive)
-        if sensitive:
-            smoothingLength.grab_focus()
-
-    def _setSmoothing(self, smoothingEnabled, smoothingLength, smoothing):
-        """Set the smoothing controls from the given value.
-
-        If the value is less than 2, smoothing is disabled. The smoothing
-        length is the absolute value of the value."""
-        smoothingEnabled.set_active(smoothing>=2)
-        smoothingLength.set_value(abs(smoothing))
-
-    def _getSmoothing(self, smoothingEnabled, smoothingLength):
-        """Get the smoothing value from the given controls.
-
-        The returned value is the value of smoothingLength multiplied by -1, if
-        smoothing is disabled."""
-        value = smoothingLength.get_value_as_int()
-        if not smoothingEnabled.get_active():
-            value *= -1
-        return value
-
-    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(text2unicode(dialog.get_filename()))
-
-    def _pirepDirectoryChanged(self, entry):
-        """Called when the PIREP directory is changed."""
-        if self._pirepDirectory.get_text():
-            self._pirepAutoSave.set_sensitive(True)
-        else:
-            self._pirepAutoSave.set_active(False)
-            self._pirepAutoSave.set_sensitive(False)
-
-    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 _buildSounds(self):
-        """Build the page for the sounds."""
-        mainAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                      xscale = 1.0, yscale = 1.0)
-        mainAlignment.set_padding(padding_top = 8, padding_bottom = 8,
-                                  padding_left = 4, padding_right = 4)
-
-        mainBox = gtk.VBox()
-        mainAlignment.add(mainBox)
-
-        backgroundFrame = gtk.Frame(label = xstr("prefs_sounds_frame_bg"))
-        mainBox.pack_start(backgroundFrame, False, False, 4)
-
-        backgroundAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                            xscale = 1.0, yscale = 0.0)
-        backgroundAlignment.set_padding(padding_top = 4, padding_bottom = 4,
-                                        padding_left = 4, padding_right = 4)
-        backgroundFrame.add(backgroundAlignment)
-
-        backgroundBox = gtk.VBox()
-        backgroundAlignment.add(backgroundBox)
-
-        self._enableSounds = gtk.CheckButton(xstr("prefs_sounds_enable"))
-        self._enableSounds.set_use_underline(True)
-        self._enableSounds.set_tooltip_text(xstr("prefs_sounds_enable_tooltip"))
-        self._enableSounds.connect("toggled", self._enableSoundsToggled)
-        alignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
-                                  xscale = 1.0, yscale = 0.0)
-        alignment.add(self._enableSounds)
-        backgroundBox.pack_start(alignment, False, False, 4)
-
-        self._pilotControlsSounds = gtk.CheckButton(xstr("prefs_sounds_pilotControls"))
-        self._pilotControlsSounds.set_use_underline(True)
-        self._pilotControlsSounds.set_tooltip_text(xstr("prefs_sounds_pilotControls_tooltip"))
-        self._pilotControlsSounds.connect("toggled", self._pilotControlsSoundsToggled)
-        backgroundBox.pack_start(self._pilotControlsSounds, False, False, 4)
-
-        self._pilotHotkey = Hotkey(xstr("prefs_sounds_pilotHotkey"),
-                                   [xstr("prefs_sounds_pilotHotkey_tooltip"),
-                                    xstr("prefs_sounds_pilotHotkeyCtrl_tooltip"),
-                                    xstr("prefs_sounds_pilotHotkeyShift_tooltip")])
-
-        backgroundBox.pack_start(self._pilotHotkey, False, False, 4)
-
-        self._enableApproachCallouts = gtk.CheckButton(xstr("prefs_sounds_approachCallouts"))
-        self._enableApproachCallouts.set_use_underline(True)
-        self._enableApproachCallouts.set_tooltip_text(xstr("prefs_sounds_approachCallouts_tooltip"))
-        backgroundBox.pack_start(self._enableApproachCallouts, False, False, 4)
-
-        self._speedbrakeAtTD = gtk.CheckButton(xstr("prefs_sounds_speedbrakeAtTD"))
-        self._speedbrakeAtTD.set_use_underline(True)
-        self._speedbrakeAtTD.set_tooltip_text(xstr("prefs_sounds_speedbrakeAtTD_tooltip"))
-        backgroundBox.pack_start(self._speedbrakeAtTD, False, False, 4)
-
-        checklistFrame = gtk.Frame(label = xstr("prefs_sounds_frame_checklists"))
-        mainBox.pack_start(checklistFrame, False, False, 4)
-
-        checklistAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
-                                           xscale = 1.0, yscale = 0.0)
-        checklistAlignment.set_padding(padding_top = 4, padding_bottom = 4,
-                                       padding_left = 4, padding_right = 4)
-        checklistFrame.add(checklistAlignment)
-
-        checklistBox = gtk.VBox()
-        checklistAlignment.add(checklistBox)
-
-        self._enableChecklists = gtk.CheckButton(xstr("prefs_sounds_enableChecklists"))
-        self._enableChecklists.set_use_underline(True)
-        self._enableChecklists.set_tooltip_text(xstr("prefs_sounds_enableChecklists_tooltip"))
-        self._enableChecklists.connect("toggled", self._enableChecklistsToggled)
-        checklistBox.pack_start(self._enableChecklists, False, False, 4)
-
-        self._checklistHotkey = Hotkey(xstr("prefs_sounds_checklistHotkey"),
-                                       [xstr("prefs_sounds_checklistHotkey_tooltip"),
-                                        xstr("prefs_sounds_checklistHotkeyCtrl_tooltip"),
-                                        xstr("prefs_sounds_checklistHotkeyShift_tooltip")])
-
-        checklistBox.pack_start(self._checklistHotkey, False, False, 4)
-
-        self._enableSoundsToggled(self._enableSounds)
-        self._enableChecklistsToggled(self._enableChecklists)
-
-        self._pilotHotkey.connect("hotkey-changed", self._reconcileHotkeys,
-                                  self._checklistHotkey)
-        self._checklistHotkey.connect("hotkey-changed", self._reconcileHotkeys,
-                                      self._pilotHotkey)
-
-        return mainAlignment
-
-    def _enableSoundsToggled(self, button):
-        """Called when the enable sounds button is toggled."""
-        active = button.get_active()
-        self._pilotControlsSounds.set_sensitive(active)
-        self._pilotControlsSoundsToggled(self._pilotControlsSounds)
-        self._enableApproachCallouts.set_sensitive(active)
-        self._speedbrakeAtTD.set_sensitive(active)
-
-    def _pilotControlsSoundsToggled(self, button):
-        """Called when the enable sounds button is toggled."""
-        active = button.get_active() and self._enableSounds.get_active()
-        self._pilotHotkey.set_sensitive(active)
-        if active and self._checklistHotkey.get_sensitive():
-            self._reconcileHotkeys(self._checklistHotkey, Hotkey.CHANGED_SHIFT,
-                                   self._pilotHotkey)
-
-    def _enableChecklistsToggled(self, button):
-        """Called when the enable checklists button is toggled."""
-        active = button.get_active()
-        self._checklistHotkey.set_sensitive(active)
-        if active and self._pilotHotkey.get_sensitive():
-            self._reconcileHotkeys(self._pilotHotkey, Hotkey.CHANGED_SHIFT,
-                                   self._checklistHotkey)
-
-    def _reconcileHotkeys(self, changedHotkey, what, otherHotkey):
-        """Reconcile the given hotkeys so that they are different.
-
-        changedHotkey is the hotkey that has changed. what is one of the
-        Hotkey.CHANGED_XXX constants denoting what has changed. otherHotkey is
-        the other hotkey that must be reconciled.
-
-        If the other hotkey is not sensitive or is not equal to the changed
-        one, nothing happens.
-
-        Otherwise, if the status of the Ctrl modifier has changed, the status
-        of the Ctrl modifier on the other hotkey will be negated. Similarly, if
-        the Shift modifier has changed. If the key has changed, the Shift
-        modifier is negated in the other hotkey."""
-        if otherHotkey.get_sensitive() and changedHotkey==otherHotkey:
-            if what==Hotkey.CHANGED_CTRL:
-                otherHotkey.ctrl = not changedHotkey.ctrl
-            elif what==Hotkey.CHANGED_SHIFT or what==Hotkey.CHANGED_KEY:
-                otherHotkey.shift = not changedHotkey.shift
-
-    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
-
-        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._settingFromConfig 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 679)
+++ src/mlx/gui/statusbar.py	(revision 679)
@@ -0,0 +1,152 @@
+
+from common import *
+
+import mlx.const as const
+from mlx.i18n import xstr
+
+import math
+import time
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.statusbar
+#
+# The status bar.
+#
+# This module implements the status bar seen at the lower part of the main
+# window. It contains the icon depicting the status of the connection to the
+# simulator, the current flight stage, rating and the simulator time.
+
+#-------------------------------------------------------------------------------
+
+class Statusbar(gtk.Frame, FlightStatusHandler):
+    """A status bar for the logger."""
+    def __init__(self, iconDirectory):
+        """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)
+
+        iconPath = os.path.join(iconDirectory, "conn_grey.png")
+        self._connGreyIcon = pixbuf_new_from_file(iconPath)
+
+        iconPath = os.path.join(iconDirectory, "conn_red.png")
+        self._connRedIcon = pixbuf_new_from_file(iconPath)
+
+        iconPath = os.path.join(iconDirectory, "conn_green.png")
+        self._connGreenIcon = pixbuf_new_from_file(iconPath)
+
+        self._connStateArea = gtk.DrawingArea()
+        self._connStateArea.set_size_request(18, 18)
+        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(20)
+        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."""
+        if self._connecting:
+            if self._connected:
+                icon = self._connGreenIcon
+            else:
+                icon = self._connRedIcon
+        else:
+            icon = self._connGreyIcon
+
+        if pygobject:
+            gdk.cairo_set_source_pixbuf(eventOrContext, icon, 0, 0)
+            eventOrContext.paint()
+        else:
+            gc = connStateArea.get_style().fg_gc[gtk.STATE_NORMAL]
+            drawable = connStateArea.get_window()
+            drawable.draw_pixbuf(gc, icon, 0, 0, 0, 0)
+
+    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 = "%.1f%%" % (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 679)
+++ 	(revision )
@@ -1,152 +1,0 @@
-
-from common import *
-
-import mlx.const as const
-from mlx.i18n import xstr
-
-import math
-import time
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.statusbar
-#
-# The status bar.
-#
-# This module implements the status bar seen at the lower part of the main
-# window. It contains the icon depicting the status of the connection to the
-# simulator, the current flight stage, rating and the simulator time.
-
-#-------------------------------------------------------------------------------
-
-class Statusbar(gtk.Frame, FlightStatusHandler):
-    """A status bar for the logger."""
-    def __init__(self, iconDirectory):
-        """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)
-
-        iconPath = os.path.join(iconDirectory, "conn_grey.png")
-        self._connGreyIcon = pixbuf_new_from_file(iconPath)
-
-        iconPath = os.path.join(iconDirectory, "conn_red.png")
-        self._connRedIcon = pixbuf_new_from_file(iconPath)
-
-        iconPath = os.path.join(iconDirectory, "conn_green.png")
-        self._connGreenIcon = pixbuf_new_from_file(iconPath)
-
-        self._connStateArea = gtk.DrawingArea()
-        self._connStateArea.set_size_request(18, 18)
-        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(20)
-        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."""
-        if self._connecting:
-            if self._connected:
-                icon = self._connGreenIcon
-            else:
-                icon = self._connRedIcon
-        else:
-            icon = self._connGreyIcon
-
-        if pygobject:
-            gdk.cairo_set_source_pixbuf(eventOrContext, icon, 0, 0)
-            eventOrContext.paint()
-        else:
-            gc = connStateArea.get_style().fg_gc[gtk.STATE_NORMAL]
-            drawable = connStateArea.get_window()
-            drawable.draw_pixbuf(gc, icon, 0, 0, 0, 0)
-
-    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 = "%.1f%%" % (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 544)
+++ src/mlx/gui/statusicon.py	(revision 544)
@@ -0,0 +1,177 @@
+
+from common import *
+
+import mlx.const as const
+from mlx.i18n import xstr
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.statusicon
+#
+# The status icon.
+#
+# This module implements that status icon displayed on the Windows or the GNOME
+# taskbar, or whatever the place for status icons is called in the used
+# environment. It works with both the more modern appindicator interface
+# (mainly found on Ubuntu), if that is available, or with the older status icon
+# support in Gtk (which is used on Windows as well). In this latter case, the
+# icon has a tooltip with the flight stage and rating information, while these
+# data are placed into the menu in case of appindicator.
+
+#-------------------------------------------------------------------------------
+
+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 = "%.1f%%" % (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 544)
+++ 	(revision )
@@ -1,177 +1,0 @@
-
-from common import *
-
-import mlx.const as const
-from mlx.i18n import xstr
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.statusicon
-#
-# The status icon.
-#
-# This module implements that status icon displayed on the Windows or the GNOME
-# taskbar, or whatever the place for status icons is called in the used
-# environment. It works with both the more modern appindicator interface
-# (mainly found on Ubuntu), if that is available, or with the older status icon
-# support in Gtk (which is used on Windows as well). In this latter case, the
-# icon has a tooltip with the flight stage and rating information, while these
-# data are placed into the menu in case of appindicator.
-
-#-------------------------------------------------------------------------------
-
-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 = "%.1f%%" % (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 300)
+++ src/mlx/gui/update.py	(revision 300)
@@ -0,0 +1,289 @@
+
+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
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.update
+#
+# The GUI part of the update.
+#
+# This module implements the GUI portion of the \ref mlx.update
+# "automatic update" function. The update runs in a separate thread, which puts
+# the relevant events into the Gtk event loop.
+
+#-------------------------------------------------------------------------------
+
+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 300)
+++ 	(revision )
@@ -1,289 +1,0 @@
-
-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
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.update
-#
-# The GUI part of the update.
-#
-# This module implements the GUI portion of the \ref mlx.update
-# "automatic update" function. The update runs in a separate thread, which puts
-# the relevant events into the Gtk event loop.
-
-#-------------------------------------------------------------------------------
-
-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 303)
+++ src/mlx/gui/weighthelp.py	(revision 303)
@@ -0,0 +1,491 @@
+
+from mlx.gui.common import *
+
+from mlx.i18n import xstr
+from mlx.checks import PayloadChecker
+
+#-------------------------------------------------------------------------------
+
+## @package mlx.gui.weighthelp
+#
+# The weight calculation help tab.
+#
+# This module implements the tab containing the weight calculation help. 
+
+#-------------------------------------------------------------------------------
+
+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")
+
+        self._crew = self._gui.numCrew
+        self._pax = self._gui.numPassengers
+        self._bag = self._gui.bagWeight
+        self._cargo = self._gui.cargoWeight
+        self._mail = self._gui.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 303)
+++ 	(revision )
@@ -1,491 +1,0 @@
-
-from mlx.gui.common import *
-
-from mlx.i18n import xstr
-from mlx.checks import PayloadChecker
-
-#-------------------------------------------------------------------------------
-
-## @package mlx.gui.weighthelp
-#
-# The weight calculation help tab.
-#
-# This module implements the tab containing the weight calculation help. 
-
-#-------------------------------------------------------------------------------
-
-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")
-
-        self._crew = self._gui.numCrew
-        self._pax = self._gui.numPassengers
-        self._bag = self._gui.bagWeight
-        self._cargo = self._gui.cargoWeight
-        self._mail = self._gui.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 298)
+++ src/mlx/i18n.py	(revision 298)
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import gettext
+import os
+import traceback
+
+#------------------------------------------------------------------------------
+
+## @package mlx.i18n
+#
+# Internationalization support.
+#
+# This module handles the internationalization support in the logger. It is
+# based on the GNU gettext library, and exports the \ref xstr function that
+# returns the translation for a certain key.
+
+#------------------------------------------------------------------------------
+
+_translation = None
+_language = None
+
+#------------------------------------------------------------------------------
+
+def setLanguage(programDirectory, language):
+    """Setup the internationalization support for the given language."""
+    print "i18n.setLanguage", language
+    translation = _getTranslation(programDirectory, language)
+    fallback = _getFallbackFor(programDirectory, language)
+    if translation is None:
+        translation = fallback
+    elif fallback is not None:
+        translation.add_fallback(fallback)
+    assert translation is not None
+
+    global _translation, _language
+    _translation = translation
+    _language = language
+    
+#------------------------------------------------------------------------------
+
+def getLanguage():
+    """Get the two-letter language code."""
+    underscoreIndex = _language.find("_")
+    return _language[:underscoreIndex] if underscoreIndex>0 else _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."""
+    return _translation.ugettext(key)
+    
+#------------------------------------------------------------------------------
+
+def _getFallbackFor(programDirectory, language):
+    """Get the fallback for the given language.
+
+    If the language is English, None is returned, otherwise the English
+    language translation."""
+    if language in ["en", "en_GB"]:
+        return None
+    else:
+        return _getTranslation(programDirectory, "en")
+
+#------------------------------------------------------------------------------
+
+def _getTranslation(programDirectory, language):
+    """Get the translation for the given language."""
+    try:
+        return gettext.translation("mlx",
+                                   localedir = os.path.join(programDirectory,
+                                                            "locale"),
+                                   languages = [language])
+    except:
+        traceback.print_exc()
+        return None
+
+#------------------------------------------------------------------------------
Index: src/mlx/i18n.py
===================================================================
--- src/mlx/i18n.py	(revision 298)
+++ 	(revision )
@@ -1,80 +1,0 @@
-# -*- coding: utf-8 -*-
-
-import gettext
-import os
-import traceback
-
-#------------------------------------------------------------------------------
-
-## @package mlx.i18n
-#
-# Internationalization support.
-#
-# This module handles the internationalization support in the logger. It is
-# based on the GNU gettext library, and exports the \ref xstr function that
-# returns the translation for a certain key.
-
-#------------------------------------------------------------------------------
-
-_translation = None
-_language = None
-
-#------------------------------------------------------------------------------
-
-def setLanguage(programDirectory, language):
-    """Setup the internationalization support for the given language."""
-    print "i18n.setLanguage", language
-    translation = _getTranslation(programDirectory, language)
-    fallback = _getFallbackFor(programDirectory, language)
-    if translation is None:
-        translation = fallback
-    elif fallback is not None:
-        translation.add_fallback(fallback)
-    assert translation is not None
-
-    global _translation, _language
-    _translation = translation
-    _language = language
-    
-#------------------------------------------------------------------------------
-
-def getLanguage():
-    """Get the two-letter language code."""
-    underscoreIndex = _language.find("_")
-    return _language[:underscoreIndex] if underscoreIndex>0 else _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."""
-    return _translation.ugettext(key)
-    
-#------------------------------------------------------------------------------
-
-def _getFallbackFor(programDirectory, language):
-    """Get the fallback for the given language.
-
-    If the language is English, None is returned, otherwise the English
-    language translation."""
-    if language in ["en", "en_GB"]:
-        return None
-    else:
-        return _getTranslation(programDirectory, "en")
-
-#------------------------------------------------------------------------------
-
-def _getTranslation(programDirectory, language):
-    """Get the translation for the given language."""
-    try:
-        return gettext.translation("mlx",
-                                   localedir = os.path.join(programDirectory,
-                                                            "locale"),
-                                   languages = [language])
-    except:
-        traceback.print_exc()
-        return None
-
-#------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 634)
+++ src/mlx/logger.py	(revision 634)
@@ -0,0 +1,405 @@
+
+from fs import sendMessage
+import const
+import util
+
+import sys
+import time
+import bisect
+
+#--------------------------------------------------------------------------------------
+
+## @package mlx.logger
+#
+# The module for the logger.
+#
+# While the program itself is "logger", it contains an internal logger, which
+# maintains the textual log containing information on the various events and is
+# the reason why the program is called "logger".
+#
+# The log is made up of lines containing an optional timestamp and the text of
+# the message. A line can be updated after having been put into the log by
+# referring to its index.
+#
+# The logger object also maintains a separate set of faults and ensures that
+# one fault type has only one score, even if that fault has been reported
+# multiple times.
+
+#--------------------------------------------------------------------------------------
+
+class Logger(object):
+    """The class with the interface to log the various events.
+
+    It contains a list of entries ordered by their timestamps and their ever
+    increasing IDs."""
+
+    class Entry(object):
+        """An entry in the log."""
+
+        # The ID of the next entry to be created
+        _nextID = 1
+
+        def __init__(self, timestamp, text, showTimestamp = True,
+                     faultID = None, faultScore = 0, id = None):
+            """Construct the entry."""
+            if id is None:
+                self._id = self._nextID
+                Logger.Entry._nextID += 1
+            else:
+                self._id = id
+
+            self._timestamp = timestamp
+            self._text = text
+            self._showTimestamp = showTimestamp
+
+            self._faultID = faultID
+            self._faultScore = faultScore
+
+        @property
+        def id(self):
+            """Get the ID of the entry."""
+            return self._id
+
+        @property
+        def timestamp(self):
+            """Get the timestamp of this entry."""
+            return self._timestamp
+
+        @property
+        def timestampString(self):
+            """Get the timestamp string of this entry.
+
+            It returns None, if the timestamp of the entry is not visible."""
+            return util.getTimestampString(self._timestamp) \
+                   if self._showTimestamp else None
+
+        @property
+        def text(self):
+            """Get the text of this entry."""
+            return self._text
+
+        @property
+        def isFault(self):
+            """Determine if this is a log entry about a fault."""
+            return self._faultID is not None
+
+        @property
+        def faultID(self):
+            """Get the fault ID of the entry.
+
+            It may be None, if the entry is not a fault entry."""
+            return self._faultID
+
+        @property
+        def faultScore(self):
+            """Get the fault score of the entry, if it is a fault."""
+            return self._faultScore
+
+        def copy(self, timestamp = None, clearTimestamp = False, text = None,
+                 faultID = None, faultScore = None, clearFault = False):
+            """Create a copy of this entry with the given values changed."""
+            return Logger.Entry(None if clearTimestamp
+                                else self._timestamp if timestamp is None
+                                else timestamp,
+
+                                self._text if text is None else text,
+
+                                showTimestamp = self._showTimestamp,
+
+                                faultID =
+                                None if clearFault
+                                else self._faultID if faultID is None
+                                else faultID,
+
+                                faultScore =
+                                None if clearFault
+                                else self._faultScore if faultScore is None
+                                else faultScore,
+
+                                id = self._id)
+
+        def __cmp__(self, other):
+            """Compare two entries
+
+            First their timestamps are compared, and if those are equal, then
+            their IDs."""
+            result = cmp(self._timestamp, other.timestamp)
+            if result==0:
+                result = cmp(self._id, other._id)
+            return result
+
+    class Fault(object):
+        """Information about a fault.
+
+        It contains the list of log entries that belong to this fault. The list
+        is ordered so that the first element contains the entry with the
+        highest score, so that it should be easy to find the actual score."""
+        def __init__(self, entry):
+            """Construct the fault info with the given log entry as its only
+            one."""
+            self._entries = [entry]
+
+        @property
+        def score(self):
+            """Get the score of this fault, i.e. the score of the entry with
+            the highest score."""
+            return self._entries[0].faultScore if self._entries else 0
+
+        def addEntry(self, entry):
+            """Add an entry to this fault.
+
+            The entries will be sorted."""
+            entries = self._entries
+            entries.append(entry)
+            entries.sort(key = lambda entry: entry.faultScore, reverse = True)
+
+        def removeEntry(self, entry):
+            """Remove the given entry.
+
+            Returns True if at least one entry remains, False otherwise."""
+            entries = self._entries
+            for index in range(0, len(entries)):
+                if entry is entries[index]:
+                    del entries[index]
+                    break
+
+            return len(entries)>0
+
+        def getLatestEntry(self):
+            """Get the entry with the highest score."""
+            return self._entries[0]
+
+    # 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
+
+    NO_SCORE = 9999
+
+    def __init__(self, output):
+        """Construct the logger."""
+        self._entries = {}
+        self._lines = []
+
+        self._faults = {}
+
+        self._output = output
+
+    @property
+    def lines(self):
+        """Get the lines of the log."""
+        return [(entry.timestampString, entry.text) for entry in self._lines]
+
+    @property
+    def faultLineIndexes(self):
+        """Get the sorted array of the indexes of those log lines that contain
+        a fault."""
+        faultLineIndexes = []
+        lines = self._lines
+        for index in range(0, len(lines)):
+            if lines[index].isFault:
+                faultLineIndexes.append(index)
+        return faultLineIndexes
+
+    def reset(self):
+        """Reset the logger.
+
+        The faults logged so far will be cleared."""
+        self._entries = {}
+        self._lines = []
+        self._faults = {}
+
+    def message(self, timestamp, msg):
+        """Put a simple textual message into the log with the given timestamp.
+
+        Returns an ID of the message so that it could be referred to later."""
+        return self._addEntry(Logger.Entry(timestamp, msg))
+
+    def untimedMessage(self, msg):
+        """Put an untimed message into the log."""
+        timestamp = self._lines[-1].timestamp if self._lines else 0
+        return self._addEntry(Logger.Entry(timestamp, msg,
+                                           showTimestamp = False))
+
+    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:
+            messageType = \
+               const.MESSAGETYPE_INFLIGHT  if stage in \
+               [const.STAGE_CLIMB, const.STAGE_CRUISE, \
+                const.STAGE_DESCENT, const.STAGE_LANDING] \
+                else const.MESSAGETYPE_INFORMATION
+            sendMessage(messageType, "Flight stage: " + s, 3)
+
+    def fault(self, faultID, timestamp, what, score,
+              updatePrevious = False, updateID = None):
+        """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 updatePrevious is True, and an instance of the given fault is
+        already in the log, only that instance will be updated with the new
+        timestamp and score. If there are several instances, the latest one
+        (with the highest score) will be updated. If updatePrevious is True,
+        and the new score is not greater than the latest one, the ID of the
+        latest one is returned.
+
+        If updateID is given, the log entry with the given ID will be
+        'upgraded' to be a fault with the given data.
+
+        Returns an ID of the fault, or -1 if it was not logged."""
+        fault = self._faults[faultID] if faultID in self._faults else None
+
+        text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
+               else "%s" % (what,) if score==Logger.NO_SCORE \
+               else "%s (%.1f)" % (what, score)
+
+        if score==Logger.NO_SCORE:
+            score = 0
+
+        if fault is not None and score<=fault.score:
+            return fault.getLatestEntry().id if updatePrevious else -1
+
+        if updatePrevious and fault is not None:
+            latestEntry = fault.getLatestEntry()
+            id = latestEntry.id
+            newEntry = latestEntry.copy(timestamp = timestamp,
+                                        text = text,
+                                        faultScore = score)
+            self._updateEntry(id, newEntry)
+            if latestEntry.isFault:
+                self._output.updateFault(id, newEntry.timestampString, text)
+            else:
+                self._output.addFault(id, newEntry.timestampString, text)
+        elif updateID is not None:
+            id = updateID
+            oldEntry = self._entries[id]
+            newEntry = oldEntry.copy(timestamp = timestamp,
+                                     text = text, faultID = faultID,
+                                     faultScore = score)
+            self._updateEntry(id, newEntry)
+            if oldEntry.isFault:
+                self._output.updateFault(id, newEntry.timestampString, text)
+            else:
+                self._output.addFault(id, newEntry.timestampString, text)
+        else:
+            entry = Logger.Entry(timestamp, text, faultID = faultID,
+                                 faultScore = score)
+            id = self._addEntry(entry)
+            self._output.addFault(id, entry.timestampString, text)
+
+        if updateID is None:
+            (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
+                                      if score==Logger.NO_GO_SCORE \
+                                      else (const.MESSAGETYPE_FAULT, 5)
+            sendMessage(messageType, text, duration)
+
+        return id
+
+    def noGo(self, faultID, timestamp, what):
+        """Report a No-Go fault."""
+        return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
+
+    def getRating(self):
+        """Get the rating of the flight so far."""
+        totalScore = 100
+        for fault in self._faults.itervalues():
+            score = fault.score
+            if score==Logger.NO_GO_SCORE:
+                return -score
+            else:
+                totalScore -= score
+        return totalScore
+
+    def updateLine(self, id, line):
+        """Update the line with the given ID with the given string.
+
+        Note, that it does not change the status of the line as a fault!"""
+        self._updateEntry(id, self._entries[id].copy(text = line))
+
+    def clearFault(self, id, text):
+        """Update the line with the given ID to contain the given string,
+        and clear its fault state."""
+        newEntry = self._entries[id].copy(text = text, clearFault = True)
+        self._updateEntry(id, newEntry)
+        self._output.clearFault(id)
+
+    def _addEntry(self, entry):
+        """Add the given entry to the log.
+
+        @return the ID of the new entry."""
+        assert entry.id not in self._entries
+
+        self._entries[entry.id] = entry
+
+        if not self._lines or entry>self._lines[-1]:
+            index = len(self._lines)
+            self._lines.append(entry)
+        else:
+            index = bisect.bisect_left(self._lines, entry)
+            self._lines.insert(index, entry)
+
+        if entry.isFault:
+            self._addFault(entry)
+
+        self._output.insertFlightLogLine(index, entry.timestampString,
+                                         entry.text, entry.isFault)
+
+        return entry.id
+
+    def _updateEntry(self, id, newEntry):
+        """Update the entry with the given ID from the given new entry."""
+        self._removeEntry(id)
+        self._addEntry(newEntry)
+
+    def _removeEntry(self, id):
+        """Remove the entry with the given ID."""
+        assert id in self._entries
+
+        entry = self._entries[id]
+        del self._entries[id]
+
+        for index in range(len(self._lines)-1, -1, -1):
+            if self._lines[index] is entry:
+                break
+        del self._lines[index]
+
+        if entry.isFault:
+            faultID = entry.faultID
+            fault = self._faults[faultID]
+            if not fault.removeEntry(entry):
+                del self._faults[faultID]
+
+        self._output.removeFlightLogLine(index)
+
+    def _addFault(self, entry):
+        """Add the given fault entry to the fault with the given ID."""
+        faultID = entry.faultID
+        if faultID in self._faults:
+            self._faults[faultID].addEntry(entry)
+        else:
+            self._faults[faultID] = Logger.Fault(entry)
+
+#--------------------------------------------------------------------------------------
Index: src/mlx/logger.py
===================================================================
--- src/mlx/logger.py	(revision 634)
+++ 	(revision )
@@ -1,405 +1,0 @@
-
-from fs import sendMessage
-import const
-import util
-
-import sys
-import time
-import bisect
-
-#--------------------------------------------------------------------------------------
-
-## @package mlx.logger
-#
-# The module for the logger.
-#
-# While the program itself is "logger", it contains an internal logger, which
-# maintains the textual log containing information on the various events and is
-# the reason why the program is called "logger".
-#
-# The log is made up of lines containing an optional timestamp and the text of
-# the message. A line can be updated after having been put into the log by
-# referring to its index.
-#
-# The logger object also maintains a separate set of faults and ensures that
-# one fault type has only one score, even if that fault has been reported
-# multiple times.
-
-#--------------------------------------------------------------------------------------
-
-class Logger(object):
-    """The class with the interface to log the various events.
-
-    It contains a list of entries ordered by their timestamps and their ever
-    increasing IDs."""
-
-    class Entry(object):
-        """An entry in the log."""
-
-        # The ID of the next entry to be created
-        _nextID = 1
-
-        def __init__(self, timestamp, text, showTimestamp = True,
-                     faultID = None, faultScore = 0, id = None):
-            """Construct the entry."""
-            if id is None:
-                self._id = self._nextID
-                Logger.Entry._nextID += 1
-            else:
-                self._id = id
-
-            self._timestamp = timestamp
-            self._text = text
-            self._showTimestamp = showTimestamp
-
-            self._faultID = faultID
-            self._faultScore = faultScore
-
-        @property
-        def id(self):
-            """Get the ID of the entry."""
-            return self._id
-
-        @property
-        def timestamp(self):
-            """Get the timestamp of this entry."""
-            return self._timestamp
-
-        @property
-        def timestampString(self):
-            """Get the timestamp string of this entry.
-
-            It returns None, if the timestamp of the entry is not visible."""
-            return util.getTimestampString(self._timestamp) \
-                   if self._showTimestamp else None
-
-        @property
-        def text(self):
-            """Get the text of this entry."""
-            return self._text
-
-        @property
-        def isFault(self):
-            """Determine if this is a log entry about a fault."""
-            return self._faultID is not None
-
-        @property
-        def faultID(self):
-            """Get the fault ID of the entry.
-
-            It may be None, if the entry is not a fault entry."""
-            return self._faultID
-
-        @property
-        def faultScore(self):
-            """Get the fault score of the entry, if it is a fault."""
-            return self._faultScore
-
-        def copy(self, timestamp = None, clearTimestamp = False, text = None,
-                 faultID = None, faultScore = None, clearFault = False):
-            """Create a copy of this entry with the given values changed."""
-            return Logger.Entry(None if clearTimestamp
-                                else self._timestamp if timestamp is None
-                                else timestamp,
-
-                                self._text if text is None else text,
-
-                                showTimestamp = self._showTimestamp,
-
-                                faultID =
-                                None if clearFault
-                                else self._faultID if faultID is None
-                                else faultID,
-
-                                faultScore =
-                                None if clearFault
-                                else self._faultScore if faultScore is None
-                                else faultScore,
-
-                                id = self._id)
-
-        def __cmp__(self, other):
-            """Compare two entries
-
-            First their timestamps are compared, and if those are equal, then
-            their IDs."""
-            result = cmp(self._timestamp, other.timestamp)
-            if result==0:
-                result = cmp(self._id, other._id)
-            return result
-
-    class Fault(object):
-        """Information about a fault.
-
-        It contains the list of log entries that belong to this fault. The list
-        is ordered so that the first element contains the entry with the
-        highest score, so that it should be easy to find the actual score."""
-        def __init__(self, entry):
-            """Construct the fault info with the given log entry as its only
-            one."""
-            self._entries = [entry]
-
-        @property
-        def score(self):
-            """Get the score of this fault, i.e. the score of the entry with
-            the highest score."""
-            return self._entries[0].faultScore if self._entries else 0
-
-        def addEntry(self, entry):
-            """Add an entry to this fault.
-
-            The entries will be sorted."""
-            entries = self._entries
-            entries.append(entry)
-            entries.sort(key = lambda entry: entry.faultScore, reverse = True)
-
-        def removeEntry(self, entry):
-            """Remove the given entry.
-
-            Returns True if at least one entry remains, False otherwise."""
-            entries = self._entries
-            for index in range(0, len(entries)):
-                if entry is entries[index]:
-                    del entries[index]
-                    break
-
-            return len(entries)>0
-
-        def getLatestEntry(self):
-            """Get the entry with the highest score."""
-            return self._entries[0]
-
-    # 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
-
-    NO_SCORE = 9999
-
-    def __init__(self, output):
-        """Construct the logger."""
-        self._entries = {}
-        self._lines = []
-
-        self._faults = {}
-
-        self._output = output
-
-    @property
-    def lines(self):
-        """Get the lines of the log."""
-        return [(entry.timestampString, entry.text) for entry in self._lines]
-
-    @property
-    def faultLineIndexes(self):
-        """Get the sorted array of the indexes of those log lines that contain
-        a fault."""
-        faultLineIndexes = []
-        lines = self._lines
-        for index in range(0, len(lines)):
-            if lines[index].isFault:
-                faultLineIndexes.append(index)
-        return faultLineIndexes
-
-    def reset(self):
-        """Reset the logger.
-
-        The faults logged so far will be cleared."""
-        self._entries = {}
-        self._lines = []
-        self._faults = {}
-
-    def message(self, timestamp, msg):
-        """Put a simple textual message into the log with the given timestamp.
-
-        Returns an ID of the message so that it could be referred to later."""
-        return self._addEntry(Logger.Entry(timestamp, msg))
-
-    def untimedMessage(self, msg):
-        """Put an untimed message into the log."""
-        timestamp = self._lines[-1].timestamp if self._lines else 0
-        return self._addEntry(Logger.Entry(timestamp, msg,
-                                           showTimestamp = False))
-
-    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:
-            messageType = \
-               const.MESSAGETYPE_INFLIGHT  if stage in \
-               [const.STAGE_CLIMB, const.STAGE_CRUISE, \
-                const.STAGE_DESCENT, const.STAGE_LANDING] \
-                else const.MESSAGETYPE_INFORMATION
-            sendMessage(messageType, "Flight stage: " + s, 3)
-
-    def fault(self, faultID, timestamp, what, score,
-              updatePrevious = False, updateID = None):
-        """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 updatePrevious is True, and an instance of the given fault is
-        already in the log, only that instance will be updated with the new
-        timestamp and score. If there are several instances, the latest one
-        (with the highest score) will be updated. If updatePrevious is True,
-        and the new score is not greater than the latest one, the ID of the
-        latest one is returned.
-
-        If updateID is given, the log entry with the given ID will be
-        'upgraded' to be a fault with the given data.
-
-        Returns an ID of the fault, or -1 if it was not logged."""
-        fault = self._faults[faultID] if faultID in self._faults else None
-
-        text = "%s (NO GO)" % (what) if score==Logger.NO_GO_SCORE \
-               else "%s" % (what,) if score==Logger.NO_SCORE \
-               else "%s (%.1f)" % (what, score)
-
-        if score==Logger.NO_SCORE:
-            score = 0
-
-        if fault is not None and score<=fault.score:
-            return fault.getLatestEntry().id if updatePrevious else -1
-
-        if updatePrevious and fault is not None:
-            latestEntry = fault.getLatestEntry()
-            id = latestEntry.id
-            newEntry = latestEntry.copy(timestamp = timestamp,
-                                        text = text,
-                                        faultScore = score)
-            self._updateEntry(id, newEntry)
-            if latestEntry.isFault:
-                self._output.updateFault(id, newEntry.timestampString, text)
-            else:
-                self._output.addFault(id, newEntry.timestampString, text)
-        elif updateID is not None:
-            id = updateID
-            oldEntry = self._entries[id]
-            newEntry = oldEntry.copy(timestamp = timestamp,
-                                     text = text, faultID = faultID,
-                                     faultScore = score)
-            self._updateEntry(id, newEntry)
-            if oldEntry.isFault:
-                self._output.updateFault(id, newEntry.timestampString, text)
-            else:
-                self._output.addFault(id, newEntry.timestampString, text)
-        else:
-            entry = Logger.Entry(timestamp, text, faultID = faultID,
-                                 faultScore = score)
-            id = self._addEntry(entry)
-            self._output.addFault(id, entry.timestampString, text)
-
-        if updateID is None:
-            (messageType, duration) = (const.MESSAGETYPE_NOGO, 10) \
-                                      if score==Logger.NO_GO_SCORE \
-                                      else (const.MESSAGETYPE_FAULT, 5)
-            sendMessage(messageType, text, duration)
-
-        return id
-
-    def noGo(self, faultID, timestamp, what):
-        """Report a No-Go fault."""
-        return self.fault(faultID, timestamp, what, Logger.NO_GO_SCORE)
-
-    def getRating(self):
-        """Get the rating of the flight so far."""
-        totalScore = 100
-        for fault in self._faults.itervalues():
-            score = fault.score
-            if score==Logger.NO_GO_SCORE:
-                return -score
-            else:
-                totalScore -= score
-        return totalScore
-
-    def updateLine(self, id, line):
-        """Update the line with the given ID with the given string.
-
-        Note, that it does not change the status of the line as a fault!"""
-        self._updateEntry(id, self._entries[id].copy(text = line))
-
-    def clearFault(self, id, text):
-        """Update the line with the given ID to contain the given string,
-        and clear its fault state."""
-        newEntry = self._entries[id].copy(text = text, clearFault = True)
-        self._updateEntry(id, newEntry)
-        self._output.clearFault(id)
-
-    def _addEntry(self, entry):
-        """Add the given entry to the log.
-
-        @return the ID of the new entry."""
-        assert entry.id not in self._entries
-
-        self._entries[entry.id] = entry
-
-        if not self._lines or entry>self._lines[-1]:
-            index = len(self._lines)
-            self._lines.append(entry)
-        else:
-            index = bisect.bisect_left(self._lines, entry)
-            self._lines.insert(index, entry)
-
-        if entry.isFault:
-            self._addFault(entry)
-
-        self._output.insertFlightLogLine(index, entry.timestampString,
-                                         entry.text, entry.isFault)
-
-        return entry.id
-
-    def _updateEntry(self, id, newEntry):
-        """Update the entry with the given ID from the given new entry."""
-        self._removeEntry(id)
-        self._addEntry(newEntry)
-
-    def _removeEntry(self, id):
-        """Remove the entry with the given ID."""
-        assert id in self._entries
-
-        entry = self._entries[id]
-        del self._entries[id]
-
-        for index in range(len(self._lines)-1, -1, -1):
-            if self._lines[index] is entry:
-                break
-        del self._lines[index]
-
-        if entry.isFault:
-            faultID = entry.faultID
-            fault = self._faults[faultID]
-            if not fault.removeEntry(entry):
-                del self._faults[faultID]
-
-        self._output.removeFlightLogLine(index)
-
-    def _addFault(self, entry):
-        """Add the given fault entry to the fault with the given ID."""
-        faultID = entry.faultID
-        if faultID in self._faults:
-            self._faults[faultID].addEntry(entry)
-        else:
-            self._faults[faultID] = Logger.Fault(entry)
-
-#--------------------------------------------------------------------------------------
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 573)
+++ src/mlx/mlx.py	(revision 573)
@@ -0,0 +1,114 @@
+
+from config import Config
+from i18n import setLanguage
+from sound import initializeSound, finalizeSound
+from util import secondaryInstallation
+from const import VERSION
+from watchdog import Watchdog
+
+import os
+import sys
+
+#--------------------------------------------------------------------------------------
+
+## @package mlx.mlx
+#
+# The main program.
+#
+# This module contains the main program of the logger as well as the \ref
+# restart "restart" handling.
+
+#--------------------------------------------------------------------------------------
+
+instance = None
+
+#--------------------------------------------------------------------------------------
+
+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
+
+    instance.close()
+    os.execv(programPath, args)
+
+#--------------------------------------------------------------------------------------
+
+def main():
+    """The main operation of the program."""
+    from singleton import SingleInstance, raiseCallbackWrapper
+
+    global instance
+    instance = SingleInstance("mlx" + ("-secondary" if secondaryInstallation
+                                       else ""), raiseCallbackWrapper)
+    if not instance: return
+
+    programDirectory = os.path.dirname(sys.argv[0])
+
+    config = Config()
+    config.load()
+
+    secondaryArgs = ["secondary"] if secondaryInstallation else []
+    if "usedeflang" not in sys.argv and config.setupLocale():
+        restart(["usedeflang"] + secondaryArgs)
+
+    setLanguage(programDirectory, config.getLanguage())
+
+    from .gui.gui import GUI
+    gui = GUI(programDirectory, config)
+
+    sys.stdout = StdIOHandler(gui)
+    sys.stderr = StdIOHandler(gui)
+
+    print "MAVA Logger X " + VERSION + " debug log"
+    print "The initial configuration:"
+    config.log()
+
+    initializeSound(os.path.join(programDirectory, "sounds"))
+
+    try:
+        Watchdog().start()
+
+        gui.build(programDirectory)
+
+        gui.run()
+
+        finalizeSound()
+    finally:
+        gui.flushStdIO()
+        sys.stdout = sys.__stdout__
+        sys.stderr = sys.__stderr__
+
+    config.save()
+
+    if gui.toRestart:
+        restart(secondaryArgs)
+
+#--------------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    if instance:
+        main()
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 573)
+++ 	(revision )
@@ -1,114 +1,0 @@
-
-from config import Config
-from i18n import setLanguage
-from sound import initializeSound, finalizeSound
-from util import secondaryInstallation
-from const import VERSION
-from watchdog import Watchdog
-
-import os
-import sys
-
-#--------------------------------------------------------------------------------------
-
-## @package mlx.mlx
-#
-# The main program.
-#
-# This module contains the main program of the logger as well as the \ref
-# restart "restart" handling.
-
-#--------------------------------------------------------------------------------------
-
-instance = None
-
-#--------------------------------------------------------------------------------------
-
-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
-
-    instance.close()
-    os.execv(programPath, args)
-
-#--------------------------------------------------------------------------------------
-
-def main():
-    """The main operation of the program."""
-    from singleton import SingleInstance, raiseCallbackWrapper
-
-    global instance
-    instance = SingleInstance("mlx" + ("-secondary" if secondaryInstallation
-                                       else ""), raiseCallbackWrapper)
-    if not instance: return
-
-    programDirectory = os.path.dirname(sys.argv[0])
-
-    config = Config()
-    config.load()
-
-    secondaryArgs = ["secondary"] if secondaryInstallation else []
-    if "usedeflang" not in sys.argv and config.setupLocale():
-        restart(["usedeflang"] + secondaryArgs)
-
-    setLanguage(programDirectory, config.getLanguage())
-
-    from .gui.gui import GUI
-    gui = GUI(programDirectory, config)
-
-    sys.stdout = StdIOHandler(gui)
-    sys.stderr = StdIOHandler(gui)
-
-    print "MAVA Logger X " + VERSION + " debug log"
-    print "The initial configuration:"
-    config.log()
-
-    initializeSound(os.path.join(programDirectory, "sounds"))
-
-    try:
-        Watchdog().start()
-
-        gui.build(programDirectory)
-
-        gui.run()
-
-        finalizeSound()
-    finally:
-        gui.flushStdIO()
-        sys.stdout = sys.__stdout__
-        sys.stderr = sys.__stderr__
-
-    config.save()
-
-    if gui.toRestart:
-        restart(secondaryArgs)
-
-#--------------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    if instance:
-        main()
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 437)
+++ src/mlx/pirep.py	(revision 437)
@@ -0,0 +1,153 @@
+
+from util import utf2unicode
+
+import const
+import cPickle as pickle
+
+#------------------------------------------------------------------------------
+
+## @package mlx.pirep
+#
+# The PIREP module.
+#
+# This module defines only one class, \ref PIREP. It is used to extract and
+# store the information needed for a PIREP. The saved PIREPs are pickled
+# instances of this class.
+
+#------------------------------------------------------------------------------
+
+class PIREP(object):
+    """A pilot's report of a flight."""
+    @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:
+                pirep = pickle.load(f)
+                if "numCrew" not in dir(pirep):
+                    pirep.numCrew = pirep.bookedFlight.numCrew
+                if "numPassengers" not in dir(pirep):
+                    pirep.numPassengers = pirep.bookedFlight.numPassengers
+                if "bagWeight" not in dir(pirep):
+                    pirep.bagWeight = pirep.bookedFlight.bagWeight
+                if "mailWeight" not in dir(pirep):
+                    pirep.mailWeight = pirep.bookedFlight.mailWeight
+                return pirep
+        except Exception, e:
+            print "Failed loading PIREP from %s: %s" % (path,
+                                                        utf2unicode(str(e)))
+            return None
+
+    def __init__(self, flight):
+        """Initialize the PIREP from the given flight."""
+        self.bookedFlight = flight.bookedFlight
+
+        self.numCrew = flight.numCrew
+        self.numPassengers = flight.numPassengers
+        self.bagWeight = flight.bagWeight
+        self.cargoWeight = flight.cargoWeight
+        self.mailWeight = flight.mailWeight
+
+        self.filedCruiseAltitude = flight.filedCruiseAltitude
+        self.cruiseAltitude = flight.cruiseAltitude
+        self.route = flight.route
+
+        self.departureMETAR = flight.departureMETAR.upper()
+        self.arrivalMETAR = flight.arrivalMETAR.upper()
+
+        self.departureRunway = flight.departureRunway.upper()
+        self.sid = flight.sid.upper()
+
+        self.star = flight.star
+        self.transition = flight.transition
+        self.approachType = flight.approachType.upper()
+        self.arrivalRunway = flight.arrivalRunway.upper()
+
+        self.flightType = flight.flightType
+        self.online = flight.online
+
+        self.comments = flight.comments
+        self.flightDefects = flight.flightDefects
+        self.delayCodes = flight.delayCodes
+
+        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 = flight.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 += 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 None
+        except Exception, e:
+            error = utf2unicode(str(e))
+            print u"Failed saving PIREP to %s: %s" % (path, error)
+            return error
Index: src/mlx/pirep.py
===================================================================
--- src/mlx/pirep.py	(revision 437)
+++ 	(revision )
@@ -1,153 +1,0 @@
-
-from util import utf2unicode
-
-import const
-import cPickle as pickle
-
-#------------------------------------------------------------------------------
-
-## @package mlx.pirep
-#
-# The PIREP module.
-#
-# This module defines only one class, \ref PIREP. It is used to extract and
-# store the information needed for a PIREP. The saved PIREPs are pickled
-# instances of this class.
-
-#------------------------------------------------------------------------------
-
-class PIREP(object):
-    """A pilot's report of a flight."""
-    @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:
-                pirep = pickle.load(f)
-                if "numCrew" not in dir(pirep):
-                    pirep.numCrew = pirep.bookedFlight.numCrew
-                if "numPassengers" not in dir(pirep):
-                    pirep.numPassengers = pirep.bookedFlight.numPassengers
-                if "bagWeight" not in dir(pirep):
-                    pirep.bagWeight = pirep.bookedFlight.bagWeight
-                if "mailWeight" not in dir(pirep):
-                    pirep.mailWeight = pirep.bookedFlight.mailWeight
-                return pirep
-        except Exception, e:
-            print "Failed loading PIREP from %s: %s" % (path,
-                                                        utf2unicode(str(e)))
-            return None
-
-    def __init__(self, flight):
-        """Initialize the PIREP from the given flight."""
-        self.bookedFlight = flight.bookedFlight
-
-        self.numCrew = flight.numCrew
-        self.numPassengers = flight.numPassengers
-        self.bagWeight = flight.bagWeight
-        self.cargoWeight = flight.cargoWeight
-        self.mailWeight = flight.mailWeight
-
-        self.filedCruiseAltitude = flight.filedCruiseAltitude
-        self.cruiseAltitude = flight.cruiseAltitude
-        self.route = flight.route
-
-        self.departureMETAR = flight.departureMETAR.upper()
-        self.arrivalMETAR = flight.arrivalMETAR.upper()
-
-        self.departureRunway = flight.departureRunway.upper()
-        self.sid = flight.sid.upper()
-
-        self.star = flight.star
-        self.transition = flight.transition
-        self.approachType = flight.approachType.upper()
-        self.arrivalRunway = flight.arrivalRunway.upper()
-
-        self.flightType = flight.flightType
-        self.online = flight.online
-
-        self.comments = flight.comments
-        self.flightDefects = flight.flightDefects
-        self.delayCodes = flight.delayCodes
-
-        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 = flight.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 += 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 None
-        except Exception, e:
-            error = utf2unicode(str(e))
-            print u"Failed saving PIREP to %s: %s" % (path, error)
-            return error
Index: src/mlx/pyuipc_emu.py
===================================================================
--- src/mlx/pyuipc_emu.py	(revision 298)
+++ src/mlx/pyuipc_emu.py	(revision 298)
@@ -0,0 +1,286 @@
+
+import time
+
+#------------------------------------------------------------------------------
+
+## @package mlx.pyuipc_emu
+#
+# A very simple PyUIPC emulator.
+#
+# This is not used currently.
+
+#------------------------------------------------------------------------------
+
+## 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 298)
+++ 	(revision )
@@ -1,286 +1,0 @@
-
-import time
-
-#------------------------------------------------------------------------------
-
-## @package mlx.pyuipc_emu
-#
-# A very simple PyUIPC emulator.
-#
-# This is not used currently.
-
-#------------------------------------------------------------------------------
-
-## 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 658)
+++ src/mlx/pyuipc_sim.py	(revision 658)
@@ -0,0 +1,1702 @@
+
+import const
+
+import cmd
+import threading
+import socket
+import time
+import calendar
+import sys
+import struct
+import cPickle
+import math
+
+#------------------------------------------------------------------------------
+
+## @package mlx.pyuipc_sim
+#
+# Simulator of the PyUIPC module.
+#
+# This is a simulation of the PyUIPC module emulating offsets that are needed
+# by the logger.
+#
+# This module can also be run as a program, in which case it connects to an
+# already running logger (that uses this module in place of the real pyuipc),
+# and various commands can be given to query or modify the values of the
+# offsets. There is a 'help' command and completion also works.
+#
+# This module is used instead of the real PyUIPC module, if the program is not
+# running on Windows or the FORCE_PYUIPC_SIM environment variable is present.
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+## 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
+SIM_FSX=8
+SIM_P3D=10
+
+#------------------------------------------------------------------------------
+
+## 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_TIP = 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
+
+    ## Engine index: engine #4
+    ENGINE_4 = 3
+
+    ## The number of hotkey entries
+    HOTKEY_SIZE = 56
+
+    @staticmethod
+    def _readFrequency(frequency):
+        """Convert the given frequency into BCD."""
+        return Values._readBCD(int(frequency*100.0))
+
+    @staticmethod
+    def _readADFFrequency(frequency):
+        """Convert the given frequency into a tuple of ADF BCD frequency
+        components."""
+        mainFrequency = Values._readBCD(int(math.floor(frequency))%1000)
+        extFrequency = (int(frequency/1000.0)*256) + (int(frequency*10.0)%10)
+
+        return [mainFrequency, extFrequency]
+
+    @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 _toADFFrequency(main, ext):
+        """Convert the given values into an ADF frequency."""
+        frequency = Values._writeBCD(main)
+        frequency += 1000.0*int(ext/256)
+        frequency += (int(ext)%16)/10.0
+        return frequency
+
+    @staticmethod
+    def _writeADFFrequency(current, value, isMain):
+        """Convert the given part of an ADF frequency into the
+        whole frequency."""
+        [mainFrequency, extFrequency] = Values._readADFFrequency(current)
+        if isMain: mainFrequency = value
+        else: extFrequency = value
+
+        return Values._toADFFrequency(mainFrequency, extFrequency)
+
+    @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, 5000.0, 5000.0,
+                               5000.0, 5000.0, 5000.0, 5000.0, 5000.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.gearControl = 1.0
+        self.noseGear = 1.0
+
+        self.spoilersArmed = False
+        self.spoilers = 0.0
+
+        self.altimeter = 1013.0
+        self.qnh = 1011.2
+
+        self.nav1 = 117.3
+        self.nav1_obs = 128
+        self.nav2 = 109.5
+        self.nav2_obs = 308
+        self.adf1 = 382.7
+        self.adf2 = 1540.6
+        self.squawk = 2200
+
+        self.windSpeed = 8.0
+        self.windDirection = 300.0
+        self.visibility = 10000
+
+        self.n1 = [0.0, 0.0, 0.0, 0.0]
+        self.throttles = [0.0, 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
+
+        self.hotkeyTable = []
+        for i in range(0, Values.HOTKEY_SIZE):
+            self.hotkeyTable.append([0, 0, 0, 0])
+
+        self.cog = 0.27
+
+        self.pmdg_737ng_switches = 0
+        self.pmdg_737ngx_lts_positionsw = 0
+
+        self.xpdrC = False
+
+        self.apMaster = False
+        self.apHeadingHold = False
+        self.apHeading = 124
+        self.apAltitudeHold = False
+        self.apAltitude = 7000
+
+        self.elevatorTrim = 0.0
+
+        self.eng1DeIce = False
+        self.eng2DeIce = False
+        self.eng3DeIce = False
+        self.eng4DeIce = False
+        self.propDeIce = False
+        self.structDeIce = False
+
+    def read(self, offset, type):
+        """Read the value at the given offset."""
+        try:
+            return self._read(offset, type)
+        except Exception, e:
+            print "failed to read offset %04x: %s" % (offset, str(e))
+            raise FSUIPCException(ERR_DATA)
+
+    def _getFlapsControl(self):
+        """Get the flaps control value"""
+        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]))
+
+    def _read(self, offset, type):
+        """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==0x02d4:       # ADF2 main
+            return Values._readADFFrequency(self.adf2)[0]
+        elif offset==0x02d6:       # ADF2 extended
+            return Values._readADFFrequency(self.adf2)[1]
+        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==0x034c:       # ADF1 main
+            return Values._readADFFrequency(self.adf1)[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==0x0356:       # ADF1 extended
+            return Values._readADFFrequency(self.adf1)[1]
+        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==0x07bc:       # AP Master switch
+            return 1 if self.apMaster else 0
+        elif offset==0x07c8:       # AP heading hold
+            return 1 if self.apHeadingHold else 0
+        elif offset==0x07cc:       # AP heading
+            return int(self.apHeading * 65536.0 / 360.0)
+        elif offset==0x07d0:       # AP altitude hold
+            return 1 if self.apAltitudeHold else 0
+        elif offset==0x07d4:       # AP altitude
+            return int(self.apAltitude * const.FEETTOMETRES * 65536.0)
+        elif offset==0x088c:       # Engine #1 throttle
+            return self._getThrottle(self.ENGINE_1)
+        elif offset==0x08b2:       # Engine #1 de-ice
+            return 1 if self.eng1DeIce else 0
+        elif offset==0x0924:       # Engine #2 throttle
+            return self._getThrottle(self.ENGINE_2)
+        elif offset==0x094a:       # Engine #2 de-ice
+            return 1 if self.eng2DeIce else 0
+        elif offset==0x09bc:       # Engine #3 throttle
+            return self._getThrottle(self.ENGINE_3)
+        elif offset==0x09e2:       # Engine #3 de-ice
+            return 1 if self.eng3DeIce else 0
+        elif offset==0x0a54:       # Engine #4 throttle
+            return self._getThrottle(self.ENGINE_4)
+        elif offset==0x0a7a:       # Engine #4 de-ice
+            return 1 if self.eng4DeIce else 0
+        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
+            return self._getFlapsControl()
+        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
+            return self.flaps * 16383.0 / self.flapsNotches[-1]
+        elif offset==0x0be8:       # Gear control
+            return int(self.gearControl * 16383.0)
+        elif offset==0x0bec:       # Nose gear
+            return int(self.noseGear * 16383.0)
+        elif offset==0x0c4e:       # NAV1 OBS
+            return self.nav1_obs
+        elif offset==0x0c5e:       # NAV2 OBS
+            return self.nav2_obs
+        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==0x0ec6:       # QNH
+            return int(self.qnh * 16.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==0x2300:       # Engine #4 N1
+            return self.n1[self.ENGINE_3]
+        elif offset==0x2ea0:       # Elevator trim
+            return self.elevatorTrim * math.pi / 180.0
+        elif offset==0x2ef8:       # Centre of Gravity
+            return self.cog
+        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==0x320c:
+            return Values.HOTKEY_SIZE
+        elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
+            tableOffset = offset - 0x3210
+            hotkeyIndex = tableOffset / 4
+            index = tableOffset % 4
+            if type=="b" or type=="c":
+                return self.hotkeyTable[hotkeyIndex][index]
+            elif type=="d" or type=="u":
+                if index==0:
+                    hotkey = self.hotkeyTable[hotkeyIndex]
+                    value = hotkey[3]
+                    value <<= 8
+                    value |= hotkey[2]
+                    value <<= 8
+                    value |= hotkey[1]
+                    value <<= 8
+                    value |= hotkey[0]
+                    return value
+                else:
+                    print "Unhandled offset: %04x" % (offset,)
+                    raise FSUIPCException(ERR_DATA)
+            else:
+                print "Unhandled offset: %04x" % (offset,)
+                raise FSUIPCException(ERR_DATA)
+        elif offset==0x32fa:       # Message duration
+            return self.messageDuration
+        elif offset==0x337c:       # Prop de-ice
+            return 1 if self.propDeIce else 0
+        elif offset==0x337d:       # Structural de-ice
+            return 1 if self.structDeIce else 0
+        elif offset==0x3380:       # Message
+            return self.message
+        elif offset==0x3364:       # Frozen
+            return 1 if self.frozen else 0
+        elif offset==0x3414:       # Flaps axis
+            return self._getFlapsControl()
+        elif offset==0x3bfa:       # Flaps increment
+            return 16383 / (len(self.flapsNotches)-1)
+        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
+        elif offset==0x6202:       # PMDG 737NG switches
+            return self.pmdg_737ng_switches
+        elif offset==0x6500:       # PMDG 737NGX lights position SW
+            return self.pmdg_737ngx_lts_positionsw
+        elif offset==0x7b91:       # Transponder standby
+            return 0 if self.xpdrC else 1
+        else:
+            print "Unhandled offset: %04x" % (offset,)
+            raise FSUIPCException(ERR_DATA)
+
+    def write(self, offset, value, type):
+        """Write the value at the given offset."""
+        try:
+            return self._write(offset, value, type)
+        except Exception, e:
+            print "failed to write offset %04x: %s" % (offset, str(e))
+            raise FSUIPCException(ERR_DATA)
+
+    def _write(self, offset, value, type):
+        """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==0x02d4:       # ADF2 main
+            self.adf2 = self._writeADFFrequency(self.adf2, value, True)
+        elif offset==0x02d6:       # ADF2 main
+            self.adf2 = self._writeADFFrequency(self.adf2, value, False)
+        elif offset==0x0330:       # Altimeter
+            self.altimeter = value / 16.0
+        elif offset==0x034c:       # ADF1 main
+            self.adf1 = self._writeADFFrequency(self.adf1, value, True)
+        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==0x0356:       # ADF1 ext
+            self.adf1 = self._writeADFFrequency(self.adf1, value, False)
+        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
+            self.radioAltitude = self.altitude - 517
+        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==0x07bc:       # AP Master switch
+            self.apMaster = value!=0
+        elif offset==0x07c8:       # AP heading hold
+            self.apHeadingHold = value!=0
+        elif offset==0x07cc:       # AP heading
+            self.apHeading = value * 360.0 / 65536.0
+        elif offset==0x07d0:       # AP altitude hold
+            self.apAltitudeHold = value!=0
+        elif offset==0x07d4:       # AP altitude
+            self.apAltitude = value / const.FEETTOMETRES / 65536.0
+        elif offset==0x088c:       # Engine #1 throttle
+            self._setThrottle(self.ENGINE_1, value)
+        elif offset==0x08b2:       # Engine #1 de-ice
+            self.eng1DeIce = value!=0
+        elif offset==0x0924:       # Engine #2 throttle
+            self._setThrottle(self.ENGINE_2, value)
+        elif offset==0x094a:       # Engine #2 de-ice
+            self.eng2DeIce = value!=0
+        elif offset==0x09bc:       # Engine #3 throttle
+            self._setThrottle(self.ENGINE_3, value)
+        elif offset==0x09e2:       # Engine #3 de-ice
+            self.eng3DeIce = value!=0
+        elif offset==0x0a54:       # Engine #4 throttle
+            self._setThrottle(self.ENGINE_4, value)
+        elif offset==0x0a7a:       # Engine #4 de-ice
+            self.eng4DeIce = value!=0
+        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==0x0be8:       # Gear control
+            self.gearControl = value / 16383.0
+        elif offset==0x0bec:       # Nose gear
+            self.noseGear = value / 16383.0
+        elif offset==0x0c4e:       # NAV1 OBS
+            self.nav1_obs = value
+        elif offset==0x0c5e:       # NAV2 OBS
+            self.nav2_obs = value
+        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==0x0ec6:       # QNH
+            self.qnh = value / 16.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==0x2300:       # Engine #4 N1
+            self.n1[self.ENGINE_4] = value
+        elif offset==0x2ea0:       # Elevator trim
+            self.elevatorTrim = value * 180.0 / math.pi
+        elif offset==0x2ef8:       # Centre of Gravity
+            self.cog = value
+        elif offset==0x30c0:       # Gross weight
+            raise FSUIPCException(ERR_DATA)
+        elif offset==0x31e4:       # Radio altitude
+            self.radioAltitude = value / const.FEETTOMETRES / 65536.0
+            self.altitude = self.radioAltitude + 517
+        elif offset==0x31e4:       # Radio altitude
+            raise FSUIPCException(ERR_DATA)
+        elif offset==0x320c:
+            return Values.HOTKEY_SIZE
+        elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
+            tableOffset = offset - 0x3210
+            hotkeyIndex = tableOffset / 4
+            index = tableOffset % 4
+            if type=="b" or type=="c":
+                self.hotkeyTable[hotkeyIndex][index] = value
+            elif type=="d" or type=="u":
+                if index==0:
+                    hotkey = self.hotkeyTable[hotkeyIndex]
+                    hotkey[0] = value & 0xff
+                    hotkey[1] = (value>>8) & 0xff
+                    hotkey[2] = (value>>16) & 0xff
+                    hotkey[3] = (value>>24) & 0xff
+                else:
+                    print "Unhandled offset: %04x for type '%s'" % (offset, type)
+                    raise FSUIPCException(ERR_DATA)
+            else:
+                print "Unhandled offset: %04x for type '%s'" % (offset, type)
+                raise FSUIPCException(ERR_DATA)
+        elif offset==0x32fa:       # Message duration
+            self.messageDuration = value
+        elif offset==0x3364:       # Frozen
+            self.frozen = value!=0
+        elif offset==0x337c:       # Propeller de-ice
+            self.propDeIce = value!=0
+        elif offset==0x337d:       # Structural de-ice
+            self.structDeIce = value!=0
+        elif offset==0x3380:       # Message
+            self.message = value
+        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
+        elif offset==0x6202:       # PMDG 737NG switches
+            self.pmdg_737ng_switches = value
+        elif offset==0x6500:       # PMDG 737NGX lights position SW
+            self.pmdg_737ngx_lts_positionsw = value
+        elif offset==0x7b91:       # Transponder standby
+            self.xpdrC = value==0
+        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, checkOpened = True):
+    """Prepare the given pattern for reading and/or writing."""
+    if not checkOpened or opened:
+        return pattern
+    else:
+        raise FSUIPCException(ERR_NOTOPEN)
+
+#------------------------------------------------------------------------------
+
+def read(data, checkOpened = True):
+    """Read the given data."""
+    if not checkOpened or opened:
+        return [values.read(offset, type) for (offset, type) in data]
+    else:
+        raise FSUIPCException(ERR_NOTOPEN)
+
+#------------------------------------------------------------------------------
+
+def write(data, checkOpened = True):
+    """Write the given data."""
+    if not checkOpened or opened:
+        for (offset, type, value) in data:
+            values.write(offset, value, type)
+    else:
+        raise FSUIPCException(ERR_NOTOPEN)
+
+#------------------------------------------------------------------------------
+
+def close():
+    """Close the connection."""
+    global opened
+    opened = False
+
+#------------------------------------------------------------------------------
+
+PORT=15015
+
+CALL_READ=1
+CALL_WRITE=2
+CALL_SETVERSION=3
+CALL_CLOSE=4
+CALL_FAILOPEN=5
+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], checkOpened = False)
+                    elif call==CALL_WRITE:
+                        result = write(args[0], checkOpened = False)
+                    elif call==CALL_SETVERSION:
+                        global fs_version
+                        fs_version = args[0]
+                        result = None
+                    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, str(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 setVersion(self, version):
+        """Set the FS version to emulate."""
+        return self._call(CALL_SETVERSION, int(version))
+
+    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 Exception(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 value * 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
+
+    @staticmethod
+    def heading2pyuipc(heading):
+        """Convert the given heading (as a string) into a PyUIPC value."""
+        return int(float(heading) * 65536.0 / 360.0)
+
+    @staticmethod
+    def pyuipc2heading(value):
+        """Convert the given PyUIPC value into a heading."""
+        return value * 360.0 / 65536.0
+
+    @staticmethod
+    def altitude2pyuipc(altitude):
+        """Convert the given altitude (as a string) into a PyUIPC value."""
+        return int(float(altitude) * const.FEETTOMETRES * 65536.0)
+
+    @staticmethod
+    def pyuipc2altitude(value):
+        """Convert the given PyUIPC value into an altitude."""
+        return value / const.FEETTOMETRES / 65536.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["gearControl"] = ([(0x0be8, "d")],
+                                              lambda value: value * 100.0 / 16383.0,
+                                              lambda word: int(float(word) *
+                                                               16383.0 / 100.0))
+        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["altimeter"] = ([(0x0330, "H")],
+                                            lambda value: value / 16.0,
+                                            lambda word: int(float(word)*16.0))
+        self._valueHandlers["qnh"] = ([(0x0ec6, "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["nav1_obs"] = ([(0x0c4e, "H")],
+                                           lambda value: value,
+                                           lambda word: int(word))
+        self._valueHandlers["nav2"] = ([(0x0352, "H")],
+                                       Values._writeFrequency,
+                                       lambda word: Values._readFrequency(float(word)))
+        self._valueHandlers["nav2_obs"] = ([(0x0c5e, "H")],
+                                           lambda value: value,
+                                           lambda word: int(word))
+        self._valueHandlers["adf1"] = ([(0x034c, "H"), (0x0356, "H")],
+                                       lambda values:
+                                       Values._toADFFrequency(values[0],
+                                                              values[1]),
+                                       lambda word:
+                                       Values._readADFFrequency(float(word)))
+        self._valueHandlers["adf2"] = ([(0x02d4, "H"), (0x02d6, "H")],
+                                       lambda values:
+                                       Values._toADFFrequency(values[0],
+                                                              values[1]),
+                                       lambda word:
+                                       Values._readADFFrequency(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["n1_4"] = ([(0x2300, "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["throttle_4"] = ([(0x0a54, "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)
+
+        for i in range(0, Values.HOTKEY_SIZE):
+            self._valueHandlers["hotkey%d" % (i,)] = ([(0x3210 + i*4, "u")],
+                                                      lambda value: "0x%08x" % (value,),
+                                                      lambda word: long(word, 16))
+
+        self._valueHandlers["cog"] = ([(0x2ef8, "f")], lambda value: value,
+                                       lambda word: float(word))
+
+        self._valueHandlers["pmdg_737ng_switches"] = ([(0x6202, "b")],
+                                                      lambda value: value,
+                                                      lambda word: int(word))
+
+        self._valueHandlers["pmdg_737ngx_lts_positionsw"] = ([(0x6500, "b")],
+                                                             lambda value: value,
+                                                             lambda word: int(word))
+        self._valueHandlers["xpdrC"] = ([(0x7b91, "b")],
+                                        lambda value: value,
+                                        lambda word: int(word))
+
+        self._valueHandlers["apMaster"] = ([(0x07bc, "u")],
+                                           CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["apHeadingHold"] = ([(0x07c8, "u")],
+                                                CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["apHeading"] = ([(0x07cc, "H")],
+                                            CLI.pyuipc2heading,
+                                            CLI.heading2pyuipc)
+        self._valueHandlers["apAltitudeHold"] = ([(0x07d0, "u")],
+                                                 CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["apAltitude"] = ([(0x07d4, "H")],
+                                             CLI.pyuipc2altitude,
+                                             CLI.altitude2pyuipc)
+
+        self._valueHandlers["trim"] = ([(0x2ea0, "f")],
+                                       lambda value: value * 180.0 / math.pi,
+                                       lambda word:
+                                       float(word) * math.pi / 180.0)
+
+        self._valueHandlers["eng1Deice"] = ([(0x08b2, "H")],
+                                            CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["eng2Deice"] = ([(0x094a, "H")],
+                                            CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["eng3Deice"] = ([(0x09e2, "H")],
+                                            CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["eng4Deice"] = ([(0x0a7a, "H")],
+                                            CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["propDeice"] = ([(0x337c, "b")],
+                                            CLI.bool2str, CLI.str2bool)
+        self._valueHandlers["structDeice"] = ([(0x337d, "b")],
+                                              CLI.bool2str, CLI.str2bool)
+
+    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 += valueHandler[0]
+
+        try:
+            results = self._client.read(data)
+            index = 0
+            i = 0
+            while index<len(results):
+                name = names[i]
+                valueHandler = self._valueHandlers[name]
+                numResults = len(valueHandler[0])
+                thisResults = results[index:index+numResults]
+                value = valueHandler[1](thisResults[0] if numResults==1
+                                        else thisResults)
+
+                print name + "=" + str(value)
+
+                index += numResults
+                i+=1
+        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 = []
+        inWord = False
+        inQuote = False
+        word = ""
+        for c in args:
+            if c.isspace() and not inQuote:
+                if inWord:
+                    arguments.append(word)
+                    word = ""
+                    inWord = False
+            elif c=='"':
+                inQuote = not inQuote
+            else:
+                inWord = True
+                word += c
+
+        if inWord:
+            arguments.append(word)
+
+        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:
+                values = valueHandler[2](value)
+                if len(valueHandler[0])==1:
+                    values = [values]
+                index = 0
+                for (offset, type) in valueHandler[0]:
+                    data.append((offset, type, values[index]))
+                    index += 1
+            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_setversion(self, args):
+        """Set the version number to simulate"""
+        try:
+            value = int(args)
+            self._client.setVersion(value)
+            print "Emulating version %d" % (value,)
+        except Exception, e:
+            print >> sys.stderr, "Failed to set the version: " + str(e)
+
+    def help_setversion(self, usage = False):
+        """Help for the setversion command"""
+        if usage: print "Usage:",
+        print "setversion <number>"
+
+    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 command"""
+        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 658)
+++ 	(revision )
@@ -1,1702 +1,0 @@
-
-import const
-
-import cmd
-import threading
-import socket
-import time
-import calendar
-import sys
-import struct
-import cPickle
-import math
-
-#------------------------------------------------------------------------------
-
-## @package mlx.pyuipc_sim
-#
-# Simulator of the PyUIPC module.
-#
-# This is a simulation of the PyUIPC module emulating offsets that are needed
-# by the logger.
-#
-# This module can also be run as a program, in which case it connects to an
-# already running logger (that uses this module in place of the real pyuipc),
-# and various commands can be given to query or modify the values of the
-# offsets. There is a 'help' command and completion also works.
-#
-# This module is used instead of the real PyUIPC module, if the program is not
-# running on Windows or the FORCE_PYUIPC_SIM environment variable is present.
-
-#------------------------------------------------------------------------------
-#------------------------------------------------------------------------------
-
-## 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
-SIM_FSX=8
-SIM_P3D=10
-
-#------------------------------------------------------------------------------
-
-## 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_TIP = 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
-
-    ## Engine index: engine #4
-    ENGINE_4 = 3
-
-    ## The number of hotkey entries
-    HOTKEY_SIZE = 56
-
-    @staticmethod
-    def _readFrequency(frequency):
-        """Convert the given frequency into BCD."""
-        return Values._readBCD(int(frequency*100.0))
-
-    @staticmethod
-    def _readADFFrequency(frequency):
-        """Convert the given frequency into a tuple of ADF BCD frequency
-        components."""
-        mainFrequency = Values._readBCD(int(math.floor(frequency))%1000)
-        extFrequency = (int(frequency/1000.0)*256) + (int(frequency*10.0)%10)
-
-        return [mainFrequency, extFrequency]
-
-    @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 _toADFFrequency(main, ext):
-        """Convert the given values into an ADF frequency."""
-        frequency = Values._writeBCD(main)
-        frequency += 1000.0*int(ext/256)
-        frequency += (int(ext)%16)/10.0
-        return frequency
-
-    @staticmethod
-    def _writeADFFrequency(current, value, isMain):
-        """Convert the given part of an ADF frequency into the
-        whole frequency."""
-        [mainFrequency, extFrequency] = Values._readADFFrequency(current)
-        if isMain: mainFrequency = value
-        else: extFrequency = value
-
-        return Values._toADFFrequency(mainFrequency, extFrequency)
-
-    @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, 5000.0, 5000.0,
-                               5000.0, 5000.0, 5000.0, 5000.0, 5000.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.gearControl = 1.0
-        self.noseGear = 1.0
-
-        self.spoilersArmed = False
-        self.spoilers = 0.0
-
-        self.altimeter = 1013.0
-        self.qnh = 1011.2
-
-        self.nav1 = 117.3
-        self.nav1_obs = 128
-        self.nav2 = 109.5
-        self.nav2_obs = 308
-        self.adf1 = 382.7
-        self.adf2 = 1540.6
-        self.squawk = 2200
-
-        self.windSpeed = 8.0
-        self.windDirection = 300.0
-        self.visibility = 10000
-
-        self.n1 = [0.0, 0.0, 0.0, 0.0]
-        self.throttles = [0.0, 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
-
-        self.hotkeyTable = []
-        for i in range(0, Values.HOTKEY_SIZE):
-            self.hotkeyTable.append([0, 0, 0, 0])
-
-        self.cog = 0.27
-
-        self.pmdg_737ng_switches = 0
-        self.pmdg_737ngx_lts_positionsw = 0
-
-        self.xpdrC = False
-
-        self.apMaster = False
-        self.apHeadingHold = False
-        self.apHeading = 124
-        self.apAltitudeHold = False
-        self.apAltitude = 7000
-
-        self.elevatorTrim = 0.0
-
-        self.eng1DeIce = False
-        self.eng2DeIce = False
-        self.eng3DeIce = False
-        self.eng4DeIce = False
-        self.propDeIce = False
-        self.structDeIce = False
-
-    def read(self, offset, type):
-        """Read the value at the given offset."""
-        try:
-            return self._read(offset, type)
-        except Exception, e:
-            print "failed to read offset %04x: %s" % (offset, str(e))
-            raise FSUIPCException(ERR_DATA)
-
-    def _getFlapsControl(self):
-        """Get the flaps control value"""
-        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]))
-
-    def _read(self, offset, type):
-        """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==0x02d4:       # ADF2 main
-            return Values._readADFFrequency(self.adf2)[0]
-        elif offset==0x02d6:       # ADF2 extended
-            return Values._readADFFrequency(self.adf2)[1]
-        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==0x034c:       # ADF1 main
-            return Values._readADFFrequency(self.adf1)[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==0x0356:       # ADF1 extended
-            return Values._readADFFrequency(self.adf1)[1]
-        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==0x07bc:       # AP Master switch
-            return 1 if self.apMaster else 0
-        elif offset==0x07c8:       # AP heading hold
-            return 1 if self.apHeadingHold else 0
-        elif offset==0x07cc:       # AP heading
-            return int(self.apHeading * 65536.0 / 360.0)
-        elif offset==0x07d0:       # AP altitude hold
-            return 1 if self.apAltitudeHold else 0
-        elif offset==0x07d4:       # AP altitude
-            return int(self.apAltitude * const.FEETTOMETRES * 65536.0)
-        elif offset==0x088c:       # Engine #1 throttle
-            return self._getThrottle(self.ENGINE_1)
-        elif offset==0x08b2:       # Engine #1 de-ice
-            return 1 if self.eng1DeIce else 0
-        elif offset==0x0924:       # Engine #2 throttle
-            return self._getThrottle(self.ENGINE_2)
-        elif offset==0x094a:       # Engine #2 de-ice
-            return 1 if self.eng2DeIce else 0
-        elif offset==0x09bc:       # Engine #3 throttle
-            return self._getThrottle(self.ENGINE_3)
-        elif offset==0x09e2:       # Engine #3 de-ice
-            return 1 if self.eng3DeIce else 0
-        elif offset==0x0a54:       # Engine #4 throttle
-            return self._getThrottle(self.ENGINE_4)
-        elif offset==0x0a7a:       # Engine #4 de-ice
-            return 1 if self.eng4DeIce else 0
-        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
-            return self._getFlapsControl()
-        elif offset==0x0be0 or offset==0x0be4:    # Flaps left and  right
-            return self.flaps * 16383.0 / self.flapsNotches[-1]
-        elif offset==0x0be8:       # Gear control
-            return int(self.gearControl * 16383.0)
-        elif offset==0x0bec:       # Nose gear
-            return int(self.noseGear * 16383.0)
-        elif offset==0x0c4e:       # NAV1 OBS
-            return self.nav1_obs
-        elif offset==0x0c5e:       # NAV2 OBS
-            return self.nav2_obs
-        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==0x0ec6:       # QNH
-            return int(self.qnh * 16.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==0x2300:       # Engine #4 N1
-            return self.n1[self.ENGINE_3]
-        elif offset==0x2ea0:       # Elevator trim
-            return self.elevatorTrim * math.pi / 180.0
-        elif offset==0x2ef8:       # Centre of Gravity
-            return self.cog
-        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==0x320c:
-            return Values.HOTKEY_SIZE
-        elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
-            tableOffset = offset - 0x3210
-            hotkeyIndex = tableOffset / 4
-            index = tableOffset % 4
-            if type=="b" or type=="c":
-                return self.hotkeyTable[hotkeyIndex][index]
-            elif type=="d" or type=="u":
-                if index==0:
-                    hotkey = self.hotkeyTable[hotkeyIndex]
-                    value = hotkey[3]
-                    value <<= 8
-                    value |= hotkey[2]
-                    value <<= 8
-                    value |= hotkey[1]
-                    value <<= 8
-                    value |= hotkey[0]
-                    return value
-                else:
-                    print "Unhandled offset: %04x" % (offset,)
-                    raise FSUIPCException(ERR_DATA)
-            else:
-                print "Unhandled offset: %04x" % (offset,)
-                raise FSUIPCException(ERR_DATA)
-        elif offset==0x32fa:       # Message duration
-            return self.messageDuration
-        elif offset==0x337c:       # Prop de-ice
-            return 1 if self.propDeIce else 0
-        elif offset==0x337d:       # Structural de-ice
-            return 1 if self.structDeIce else 0
-        elif offset==0x3380:       # Message
-            return self.message
-        elif offset==0x3364:       # Frozen
-            return 1 if self.frozen else 0
-        elif offset==0x3414:       # Flaps axis
-            return self._getFlapsControl()
-        elif offset==0x3bfa:       # Flaps increment
-            return 16383 / (len(self.flapsNotches)-1)
-        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
-        elif offset==0x6202:       # PMDG 737NG switches
-            return self.pmdg_737ng_switches
-        elif offset==0x6500:       # PMDG 737NGX lights position SW
-            return self.pmdg_737ngx_lts_positionsw
-        elif offset==0x7b91:       # Transponder standby
-            return 0 if self.xpdrC else 1
-        else:
-            print "Unhandled offset: %04x" % (offset,)
-            raise FSUIPCException(ERR_DATA)
-
-    def write(self, offset, value, type):
-        """Write the value at the given offset."""
-        try:
-            return self._write(offset, value, type)
-        except Exception, e:
-            print "failed to write offset %04x: %s" % (offset, str(e))
-            raise FSUIPCException(ERR_DATA)
-
-    def _write(self, offset, value, type):
-        """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==0x02d4:       # ADF2 main
-            self.adf2 = self._writeADFFrequency(self.adf2, value, True)
-        elif offset==0x02d6:       # ADF2 main
-            self.adf2 = self._writeADFFrequency(self.adf2, value, False)
-        elif offset==0x0330:       # Altimeter
-            self.altimeter = value / 16.0
-        elif offset==0x034c:       # ADF1 main
-            self.adf1 = self._writeADFFrequency(self.adf1, value, True)
-        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==0x0356:       # ADF1 ext
-            self.adf1 = self._writeADFFrequency(self.adf1, value, False)
-        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
-            self.radioAltitude = self.altitude - 517
-        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==0x07bc:       # AP Master switch
-            self.apMaster = value!=0
-        elif offset==0x07c8:       # AP heading hold
-            self.apHeadingHold = value!=0
-        elif offset==0x07cc:       # AP heading
-            self.apHeading = value * 360.0 / 65536.0
-        elif offset==0x07d0:       # AP altitude hold
-            self.apAltitudeHold = value!=0
-        elif offset==0x07d4:       # AP altitude
-            self.apAltitude = value / const.FEETTOMETRES / 65536.0
-        elif offset==0x088c:       # Engine #1 throttle
-            self._setThrottle(self.ENGINE_1, value)
-        elif offset==0x08b2:       # Engine #1 de-ice
-            self.eng1DeIce = value!=0
-        elif offset==0x0924:       # Engine #2 throttle
-            self._setThrottle(self.ENGINE_2, value)
-        elif offset==0x094a:       # Engine #2 de-ice
-            self.eng2DeIce = value!=0
-        elif offset==0x09bc:       # Engine #3 throttle
-            self._setThrottle(self.ENGINE_3, value)
-        elif offset==0x09e2:       # Engine #3 de-ice
-            self.eng3DeIce = value!=0
-        elif offset==0x0a54:       # Engine #4 throttle
-            self._setThrottle(self.ENGINE_4, value)
-        elif offset==0x0a7a:       # Engine #4 de-ice
-            self.eng4DeIce = value!=0
-        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==0x0be8:       # Gear control
-            self.gearControl = value / 16383.0
-        elif offset==0x0bec:       # Nose gear
-            self.noseGear = value / 16383.0
-        elif offset==0x0c4e:       # NAV1 OBS
-            self.nav1_obs = value
-        elif offset==0x0c5e:       # NAV2 OBS
-            self.nav2_obs = value
-        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==0x0ec6:       # QNH
-            self.qnh = value / 16.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==0x2300:       # Engine #4 N1
-            self.n1[self.ENGINE_4] = value
-        elif offset==0x2ea0:       # Elevator trim
-            self.elevatorTrim = value * 180.0 / math.pi
-        elif offset==0x2ef8:       # Centre of Gravity
-            self.cog = value
-        elif offset==0x30c0:       # Gross weight
-            raise FSUIPCException(ERR_DATA)
-        elif offset==0x31e4:       # Radio altitude
-            self.radioAltitude = value / const.FEETTOMETRES / 65536.0
-            self.altitude = self.radioAltitude + 517
-        elif offset==0x31e4:       # Radio altitude
-            raise FSUIPCException(ERR_DATA)
-        elif offset==0x320c:
-            return Values.HOTKEY_SIZE
-        elif offset>=0x3210 and offset<0x3210+Values.HOTKEY_SIZE*4:
-            tableOffset = offset - 0x3210
-            hotkeyIndex = tableOffset / 4
-            index = tableOffset % 4
-            if type=="b" or type=="c":
-                self.hotkeyTable[hotkeyIndex][index] = value
-            elif type=="d" or type=="u":
-                if index==0:
-                    hotkey = self.hotkeyTable[hotkeyIndex]
-                    hotkey[0] = value & 0xff
-                    hotkey[1] = (value>>8) & 0xff
-                    hotkey[2] = (value>>16) & 0xff
-                    hotkey[3] = (value>>24) & 0xff
-                else:
-                    print "Unhandled offset: %04x for type '%s'" % (offset, type)
-                    raise FSUIPCException(ERR_DATA)
-            else:
-                print "Unhandled offset: %04x for type '%s'" % (offset, type)
-                raise FSUIPCException(ERR_DATA)
-        elif offset==0x32fa:       # Message duration
-            self.messageDuration = value
-        elif offset==0x3364:       # Frozen
-            self.frozen = value!=0
-        elif offset==0x337c:       # Propeller de-ice
-            self.propDeIce = value!=0
-        elif offset==0x337d:       # Structural de-ice
-            self.structDeIce = value!=0
-        elif offset==0x3380:       # Message
-            self.message = value
-        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
-        elif offset==0x6202:       # PMDG 737NG switches
-            self.pmdg_737ng_switches = value
-        elif offset==0x6500:       # PMDG 737NGX lights position SW
-            self.pmdg_737ngx_lts_positionsw = value
-        elif offset==0x7b91:       # Transponder standby
-            self.xpdrC = value==0
-        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, checkOpened = True):
-    """Prepare the given pattern for reading and/or writing."""
-    if not checkOpened or opened:
-        return pattern
-    else:
-        raise FSUIPCException(ERR_NOTOPEN)
-
-#------------------------------------------------------------------------------
-
-def read(data, checkOpened = True):
-    """Read the given data."""
-    if not checkOpened or opened:
-        return [values.read(offset, type) for (offset, type) in data]
-    else:
-        raise FSUIPCException(ERR_NOTOPEN)
-
-#------------------------------------------------------------------------------
-
-def write(data, checkOpened = True):
-    """Write the given data."""
-    if not checkOpened or opened:
-        for (offset, type, value) in data:
-            values.write(offset, value, type)
-    else:
-        raise FSUIPCException(ERR_NOTOPEN)
-
-#------------------------------------------------------------------------------
-
-def close():
-    """Close the connection."""
-    global opened
-    opened = False
-
-#------------------------------------------------------------------------------
-
-PORT=15015
-
-CALL_READ=1
-CALL_WRITE=2
-CALL_SETVERSION=3
-CALL_CLOSE=4
-CALL_FAILOPEN=5
-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], checkOpened = False)
-                    elif call==CALL_WRITE:
-                        result = write(args[0], checkOpened = False)
-                    elif call==CALL_SETVERSION:
-                        global fs_version
-                        fs_version = args[0]
-                        result = None
-                    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, str(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 setVersion(self, version):
-        """Set the FS version to emulate."""
-        return self._call(CALL_SETVERSION, int(version))
-
-    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 Exception(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 value * 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
-
-    @staticmethod
-    def heading2pyuipc(heading):
-        """Convert the given heading (as a string) into a PyUIPC value."""
-        return int(float(heading) * 65536.0 / 360.0)
-
-    @staticmethod
-    def pyuipc2heading(value):
-        """Convert the given PyUIPC value into a heading."""
-        return value * 360.0 / 65536.0
-
-    @staticmethod
-    def altitude2pyuipc(altitude):
-        """Convert the given altitude (as a string) into a PyUIPC value."""
-        return int(float(altitude) * const.FEETTOMETRES * 65536.0)
-
-    @staticmethod
-    def pyuipc2altitude(value):
-        """Convert the given PyUIPC value into an altitude."""
-        return value / const.FEETTOMETRES / 65536.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["gearControl"] = ([(0x0be8, "d")],
-                                              lambda value: value * 100.0 / 16383.0,
-                                              lambda word: int(float(word) *
-                                                               16383.0 / 100.0))
-        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["altimeter"] = ([(0x0330, "H")],
-                                            lambda value: value / 16.0,
-                                            lambda word: int(float(word)*16.0))
-        self._valueHandlers["qnh"] = ([(0x0ec6, "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["nav1_obs"] = ([(0x0c4e, "H")],
-                                           lambda value: value,
-                                           lambda word: int(word))
-        self._valueHandlers["nav2"] = ([(0x0352, "H")],
-                                       Values._writeFrequency,
-                                       lambda word: Values._readFrequency(float(word)))
-        self._valueHandlers["nav2_obs"] = ([(0x0c5e, "H")],
-                                           lambda value: value,
-                                           lambda word: int(word))
-        self._valueHandlers["adf1"] = ([(0x034c, "H"), (0x0356, "H")],
-                                       lambda values:
-                                       Values._toADFFrequency(values[0],
-                                                              values[1]),
-                                       lambda word:
-                                       Values._readADFFrequency(float(word)))
-        self._valueHandlers["adf2"] = ([(0x02d4, "H"), (0x02d6, "H")],
-                                       lambda values:
-                                       Values._toADFFrequency(values[0],
-                                                              values[1]),
-                                       lambda word:
-                                       Values._readADFFrequency(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["n1_4"] = ([(0x2300, "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["throttle_4"] = ([(0x0a54, "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)
-
-        for i in range(0, Values.HOTKEY_SIZE):
-            self._valueHandlers["hotkey%d" % (i,)] = ([(0x3210 + i*4, "u")],
-                                                      lambda value: "0x%08x" % (value,),
-                                                      lambda word: long(word, 16))
-
-        self._valueHandlers["cog"] = ([(0x2ef8, "f")], lambda value: value,
-                                       lambda word: float(word))
-
-        self._valueHandlers["pmdg_737ng_switches"] = ([(0x6202, "b")],
-                                                      lambda value: value,
-                                                      lambda word: int(word))
-
-        self._valueHandlers["pmdg_737ngx_lts_positionsw"] = ([(0x6500, "b")],
-                                                             lambda value: value,
-                                                             lambda word: int(word))
-        self._valueHandlers["xpdrC"] = ([(0x7b91, "b")],
-                                        lambda value: value,
-                                        lambda word: int(word))
-
-        self._valueHandlers["apMaster"] = ([(0x07bc, "u")],
-                                           CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["apHeadingHold"] = ([(0x07c8, "u")],
-                                                CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["apHeading"] = ([(0x07cc, "H")],
-                                            CLI.pyuipc2heading,
-                                            CLI.heading2pyuipc)
-        self._valueHandlers["apAltitudeHold"] = ([(0x07d0, "u")],
-                                                 CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["apAltitude"] = ([(0x07d4, "H")],
-                                             CLI.pyuipc2altitude,
-                                             CLI.altitude2pyuipc)
-
-        self._valueHandlers["trim"] = ([(0x2ea0, "f")],
-                                       lambda value: value * 180.0 / math.pi,
-                                       lambda word:
-                                       float(word) * math.pi / 180.0)
-
-        self._valueHandlers["eng1Deice"] = ([(0x08b2, "H")],
-                                            CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["eng2Deice"] = ([(0x094a, "H")],
-                                            CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["eng3Deice"] = ([(0x09e2, "H")],
-                                            CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["eng4Deice"] = ([(0x0a7a, "H")],
-                                            CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["propDeice"] = ([(0x337c, "b")],
-                                            CLI.bool2str, CLI.str2bool)
-        self._valueHandlers["structDeice"] = ([(0x337d, "b")],
-                                              CLI.bool2str, CLI.str2bool)
-
-    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 += valueHandler[0]
-
-        try:
-            results = self._client.read(data)
-            index = 0
-            i = 0
-            while index<len(results):
-                name = names[i]
-                valueHandler = self._valueHandlers[name]
-                numResults = len(valueHandler[0])
-                thisResults = results[index:index+numResults]
-                value = valueHandler[1](thisResults[0] if numResults==1
-                                        else thisResults)
-
-                print name + "=" + str(value)
-
-                index += numResults
-                i+=1
-        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 = []
-        inWord = False
-        inQuote = False
-        word = ""
-        for c in args:
-            if c.isspace() and not inQuote:
-                if inWord:
-                    arguments.append(word)
-                    word = ""
-                    inWord = False
-            elif c=='"':
-                inQuote = not inQuote
-            else:
-                inWord = True
-                word += c
-
-        if inWord:
-            arguments.append(word)
-
-        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:
-                values = valueHandler[2](value)
-                if len(valueHandler[0])==1:
-                    values = [values]
-                index = 0
-                for (offset, type) in valueHandler[0]:
-                    data.append((offset, type, values[index]))
-                    index += 1
-            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_setversion(self, args):
-        """Set the version number to simulate"""
-        try:
-            value = int(args)
-            self._client.setVersion(value)
-            print "Emulating version %d" % (value,)
-        except Exception, e:
-            print >> sys.stderr, "Failed to set the version: " + str(e)
-
-    def help_setversion(self, usage = False):
-        """Help for the setversion command"""
-        if usage: print "Usage:",
-        print "setversion <number>"
-
-    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 command"""
-        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/singleton.py
===================================================================
--- src/mlx/singleton.py	(revision 401)
+++ src/mlx/singleton.py	(revision 401)
@@ -0,0 +1,309 @@
+
+from util import utf2unicode
+
+import os
+import time
+
+#----------------------------------------------------------------------------
+
+## @package mlx.singleton
+#
+# Allow a single instance of an application to run.
+#
+# This module implements the logic needed to detect that an instance of the
+# application is already running, and if so, notifying that instance.
+#
+# On Windows, a mutex is used to ensure that only a single instance is running,
+# since a mutex with a certain name can be created by one process only. If the
+# current process is the only instance, a named pipe is created and a thread is
+# started to listen for messages. Otherwise a message is sent to that pipe,
+# which results in calling a callback function.
+#
+# On Linux a Unix socket is created protected by a lock file, and the messages
+# from other instances are received via this socket.
+#
+# The main window is raised if another instance is started.
+
+#----------------------------------------------------------------------------
+
+if os.name=="nt":
+    import win32event
+    import win32file
+    import win32pipe
+    import win32api
+    import winerror
+
+    import threading
+
+    class _PipeServer(threading.Thread):
+        """A server that creates a named pipe, and waits for messages on
+        that."""
+        
+        BUFFER_SIZE = 4096
+        
+        def __init__(self, pipeName, raiseCallback):
+            """Construct the server thread."""
+            super(_PipeServer, self).__init__()
+            
+            self._pipeName = pipeName
+            self._raiseCallback = raiseCallback
+            self.daemon = True
+
+        def run(self):
+            """Perform the operation of the thread."""
+            try:
+                while True:
+                    handle = self._createPipe()
+
+                    if handle is None:
+                        break
+
+                    print "singleton._PipeServer.run: created the pipe"
+                    try:
+                        if win32pipe.ConnectNamedPipe(handle)==0:
+                            print "singleton._PipeServer.run: client connection received"
+                            (code, message) = \
+                                win32file.ReadFile(handle,
+                                                   _PipeServer.BUFFER_SIZE,
+                                                   None)
+
+                            if code==0:
+                                print "singleton._PipeServer.run: message received from client"
+                                self._raiseCallback()
+                            else:
+                                print "singleton._PipeServer.run: failed to read from the pipe"
+                    except Exception, e:
+                        print "singleton._PipeServer.run: exception:",
+                        print utf2unicode(str(e))
+                    finally:
+                        win32pipe.DisconnectNamedPipe(handle)
+                        win32file.CloseHandle(handle)
+            except Exception, e:
+                print "singleton._PipeServer.run: fatal exception:",
+                print utf2unicode(str(e))
+                            
+        def _createPipe(self):
+            """Create the pipe."""
+            handle = win32pipe.CreateNamedPipe(self._pipeName,
+                                               win32pipe.PIPE_ACCESS_INBOUND,
+                                               win32pipe.PIPE_TYPE_BYTE |
+                                               win32pipe.PIPE_READMODE_BYTE |
+                                               win32pipe.PIPE_WAIT,
+                                               win32pipe.PIPE_UNLIMITED_INSTANCES,
+                                               _PipeServer.BUFFER_SIZE,
+                                               _PipeServer.BUFFER_SIZE,
+                                               1000,
+                                               None)
+            if handle==win32file.INVALID_HANDLE_VALUE:
+                print "singleton._PipeServer.run: could not create the handle"
+                return None
+            else:
+                return handle            
+
+    class SingleInstance(object):
+        """Creating an instance of this object checks if only one instance of
+        the process runs."""
+        def __init__(self, baseName, raiseCallback):
+            """Construct the single instance object.
+
+            raiseCallback is a function that will be called, if another
+            instance of the program is started."""
+            self._name = baseName + "_" + win32api.GetUserName()
+            self._mutex = win32event.CreateMutex(None, False, self._name)
+            self._isSingle = win32api.GetLastError() != \
+                             winerror.ERROR_ALREADY_EXISTS
+            if self._isSingle:
+                self._startPipeServer(raiseCallback)
+            else:
+                self._notifySingleton()
+
+        def close(self):
+            """Close the instance by closing the mutex."""
+            if self._mutex:
+                win32api.CloseHandle(self._mutex)
+                self._mutex = None
+
+        def _getPipeName(self):
+            """Get the name of the pipe to be used for communication."""
+            return r'\\.\pipe\\' + self._name
+
+        def _startPipeServer(self, raiseCallback):
+            """Start the pipe server"""
+            pipeServer = _PipeServer(self._getPipeName(),
+                                     raiseCallback)
+            pipeServer.start()
+
+        def _notifySingleton(self):
+            """Notify the already running instance of the program of our
+            presence."""
+            pipeName = self._getPipeName()
+            for i in range(0, 3):
+                try:
+                    f = open(pipeName, "wb")
+                    f.write("hello")
+                    f.close()
+                    return
+                except Exception, e:
+                    print "SingleInstance._notifySingleton: failed:",
+                    print utf2unicode(str(e))
+                    time.sleep(0.5)
+        
+        def __nonzero__(self):
+            """Return a boolean representation of the object.
+
+            It is True, if this is the single instance of the program."""
+            return self._isSingle            
+                
+        def __del__(self):
+            """Destroy the object."""
+            self.close()
+                
+#----------------------------------------------------------------------------
+
+else:     # os.name=="nt"
+    import fcntl
+    import socket
+    import tempfile
+    import threading
+    
+    class _SocketServer(threading.Thread):
+        """Server thread to handle the Unix socket through which we are
+        notified of other instances starting."""
+        def __init__(self, socketName, raiseCallback):
+            """Construct the server."""
+            super(_SocketServer, self).__init__()
+
+            self._socketName = socketName
+            self._raiseCallback = raiseCallback
+
+            self.daemon = True
+            
+
+        def run(self):
+            """Perform the thread's operation."""
+            try:
+                try:
+                    os.remove(self._socketName)
+                except:
+                    pass
+                
+                s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+                s.bind(self._socketName)
+
+                while True:
+                    s.recv(64)
+                    self._raiseCallback()
+            except Exception, e:
+                print "singleton._SocketServer.run: fatal exception:",
+                print utf2unicode(str(e))
+    
+    class SingleInstance(object):
+        """Creating an instance of this object checks if only one instance of
+        the process runs."""
+        def __init__(self, baseName, raiseCallback):
+            """Construct the single instance object.
+
+            raiseCallback is a function that will be called, if another
+            instance of the program is started."""
+            baseName = baseName + "_" + os.environ["LOGNAME"]
+
+            tempDir = tempfile.gettempdir()
+            self._lockName = os.path.join(tempDir, baseName + ".lock")
+            self._socketName = os.path.join(tempDir, baseName + ".sock")
+            
+            self._lockFile = open(self._lockName, "w")
+
+            self._isSingle = False
+            try:
+                fcntl.lockf(self._lockFile, fcntl.LOCK_EX | fcntl.LOCK_NB)
+                self._isSingle = True
+            except Exception, e:
+                self._lockFile.close()
+                self._lockFile = None
+                pass
+
+            if self._isSingle:
+                self._startSocketServer(raiseCallback)
+            else:
+                self._notifySingleton()
+
+        def close(self):
+            """Close the instance by closing the mutex."""
+            if self._isSingle:
+                if self._lockFile:
+                    self._lockFile.close()
+                    self._lockFile = None
+                    try:
+                        os.remove(self._lockName)
+                    except:
+                        pass
+                    try:
+                        os.remove(self._socketName)
+                    except:
+                        pass
+                
+
+        def _startSocketServer(self, raiseCallback):
+            """Start the pipe server"""
+            pipeServer = _SocketServer(self._socketName, raiseCallback)
+            pipeServer.start()
+
+        def _notifySingleton(self):
+            """Notify the already running instance of the program of our
+            presence."""
+            s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+            for i in range(0, 3):
+                try:
+                    s.connect(self._socketName)
+                    s.send("hello")
+                    s.close()
+                    return
+                except Exception, e:
+                    print "singleton.SingleInstance._notifySingleton: failed:",
+                    print utf2unicode(str(e))
+                    time.sleep(0.5)
+
+        def __nonzero__(self):
+            """Return a boolean representation of the object.
+
+            It is True, if this is the single instance of the program."""
+            return self._isSingle            
+
+        def __del__(self):
+            """Destroy the object."""
+            self.close()
+                
+#----------------------------------------------------------------------------
+#----------------------------------------------------------------------------
+
+# MAVA Logger X-specific stuff
+
+#----------------------------------------------------------------------------
+
+# The callback to use
+raiseCallback = None
+
+#----------------------------------------------------------------------------
+
+def raiseCallbackWrapper():
+    """The actual function to be used as the callback.
+
+    It checks if raiseCallback is None, and if not, it calls that."""
+    callback = raiseCallback
+    if callback is not None:
+        callback()
+
+#----------------------------------------------------------------------------
+#----------------------------------------------------------------------------
+
+if __name__=="__main__":
+    def raiseCallback():
+        print "Raise the window!"
+
+    instance = SingleInstance("mlx", raiseCallback)
+    if instance:
+        print "The first instance"
+        time.sleep(10)
+    else:
+        print "The program is already running."
+
Index: src/mlx/singleton.py
===================================================================
--- src/mlx/singleton.py	(revision 401)
+++ 	(revision )
@@ -1,309 +1,0 @@
-
-from util import utf2unicode
-
-import os
-import time
-
-#----------------------------------------------------------------------------
-
-## @package mlx.singleton
-#
-# Allow a single instance of an application to run.
-#
-# This module implements the logic needed to detect that an instance of the
-# application is already running, and if so, notifying that instance.
-#
-# On Windows, a mutex is used to ensure that only a single instance is running,
-# since a mutex with a certain name can be created by one process only. If the
-# current process is the only instance, a named pipe is created and a thread is
-# started to listen for messages. Otherwise a message is sent to that pipe,
-# which results in calling a callback function.
-#
-# On Linux a Unix socket is created protected by a lock file, and the messages
-# from other instances are received via this socket.
-#
-# The main window is raised if another instance is started.
-
-#----------------------------------------------------------------------------
-
-if os.name=="nt":
-    import win32event
-    import win32file
-    import win32pipe
-    import win32api
-    import winerror
-
-    import threading
-
-    class _PipeServer(threading.Thread):
-        """A server that creates a named pipe, and waits for messages on
-        that."""
-        
-        BUFFER_SIZE = 4096
-        
-        def __init__(self, pipeName, raiseCallback):
-            """Construct the server thread."""
-            super(_PipeServer, self).__init__()
-            
-            self._pipeName = pipeName
-            self._raiseCallback = raiseCallback
-            self.daemon = True
-
-        def run(self):
-            """Perform the operation of the thread."""
-            try:
-                while True:
-                    handle = self._createPipe()
-
-                    if handle is None:
-                        break
-
-                    print "singleton._PipeServer.run: created the pipe"
-                    try:
-                        if win32pipe.ConnectNamedPipe(handle)==0:
-                            print "singleton._PipeServer.run: client connection received"
-                            (code, message) = \
-                                win32file.ReadFile(handle,
-                                                   _PipeServer.BUFFER_SIZE,
-                                                   None)
-
-                            if code==0:
-                                print "singleton._PipeServer.run: message received from client"
-                                self._raiseCallback()
-                            else:
-                                print "singleton._PipeServer.run: failed to read from the pipe"
-                    except Exception, e:
-                        print "singleton._PipeServer.run: exception:",
-                        print utf2unicode(str(e))
-                    finally:
-                        win32pipe.DisconnectNamedPipe(handle)
-                        win32file.CloseHandle(handle)
-            except Exception, e:
-                print "singleton._PipeServer.run: fatal exception:",
-                print utf2unicode(str(e))
-                            
-        def _createPipe(self):
-            """Create the pipe."""
-            handle = win32pipe.CreateNamedPipe(self._pipeName,
-                                               win32pipe.PIPE_ACCESS_INBOUND,
-                                               win32pipe.PIPE_TYPE_BYTE |
-                                               win32pipe.PIPE_READMODE_BYTE |
-                                               win32pipe.PIPE_WAIT,
-                                               win32pipe.PIPE_UNLIMITED_INSTANCES,
-                                               _PipeServer.BUFFER_SIZE,
-                                               _PipeServer.BUFFER_SIZE,
-                                               1000,
-                                               None)
-            if handle==win32file.INVALID_HANDLE_VALUE:
-                print "singleton._PipeServer.run: could not create the handle"
-                return None
-            else:
-                return handle            
-
-    class SingleInstance(object):
-        """Creating an instance of this object checks if only one instance of
-        the process runs."""
-        def __init__(self, baseName, raiseCallback):
-            """Construct the single instance object.
-
-            raiseCallback is a function that will be called, if another
-            instance of the program is started."""
-            self._name = baseName + "_" + win32api.GetUserName()
-            self._mutex = win32event.CreateMutex(None, False, self._name)
-            self._isSingle = win32api.GetLastError() != \
-                             winerror.ERROR_ALREADY_EXISTS
-            if self._isSingle:
-                self._startPipeServer(raiseCallback)
-            else:
-                self._notifySingleton()
-
-        def close(self):
-            """Close the instance by closing the mutex."""
-            if self._mutex:
-                win32api.CloseHandle(self._mutex)
-                self._mutex = None
-
-        def _getPipeName(self):
-            """Get the name of the pipe to be used for communication."""
-            return r'\\.\pipe\\' + self._name
-
-        def _startPipeServer(self, raiseCallback):
-            """Start the pipe server"""
-            pipeServer = _PipeServer(self._getPipeName(),
-                                     raiseCallback)
-            pipeServer.start()
-
-        def _notifySingleton(self):
-            """Notify the already running instance of the program of our
-            presence."""
-            pipeName = self._getPipeName()
-            for i in range(0, 3):
-                try:
-                    f = open(pipeName, "wb")
-                    f.write("hello")
-                    f.close()
-                    return
-                except Exception, e:
-                    print "SingleInstance._notifySingleton: failed:",
-                    print utf2unicode(str(e))
-                    time.sleep(0.5)
-        
-        def __nonzero__(self):
-            """Return a boolean representation of the object.
-
-            It is True, if this is the single instance of the program."""
-            return self._isSingle            
-                
-        def __del__(self):
-            """Destroy the object."""
-            self.close()
-                
-#----------------------------------------------------------------------------
-
-else:     # os.name=="nt"
-    import fcntl
-    import socket
-    import tempfile
-    import threading
-    
-    class _SocketServer(threading.Thread):
-        """Server thread to handle the Unix socket through which we are
-        notified of other instances starting."""
-        def __init__(self, socketName, raiseCallback):
-            """Construct the server."""
-            super(_SocketServer, self).__init__()
-
-            self._socketName = socketName
-            self._raiseCallback = raiseCallback
-
-            self.daemon = True
-            
-
-        def run(self):
-            """Perform the thread's operation."""
-            try:
-                try:
-                    os.remove(self._socketName)
-                except:
-                    pass
-                
-                s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
-                s.bind(self._socketName)
-
-                while True:
-                    s.recv(64)
-                    self._raiseCallback()
-            except Exception, e:
-                print "singleton._SocketServer.run: fatal exception:",
-                print utf2unicode(str(e))
-    
-    class SingleInstance(object):
-        """Creating an instance of this object checks if only one instance of
-        the process runs."""
-        def __init__(self, baseName, raiseCallback):
-            """Construct the single instance object.
-
-            raiseCallback is a function that will be called, if another
-            instance of the program is started."""
-            baseName = baseName + "_" + os.environ["LOGNAME"]
-
-            tempDir = tempfile.gettempdir()
-            self._lockName = os.path.join(tempDir, baseName + ".lock")
-            self._socketName = os.path.join(tempDir, baseName + ".sock")
-            
-            self._lockFile = open(self._lockName, "w")
-
-            self._isSingle = False
-            try:
-                fcntl.lockf(self._lockFile, fcntl.LOCK_EX | fcntl.LOCK_NB)
-                self._isSingle = True
-            except Exception, e:
-                self._lockFile.close()
-                self._lockFile = None
-                pass
-
-            if self._isSingle:
-                self._startSocketServer(raiseCallback)
-            else:
-                self._notifySingleton()
-
-        def close(self):
-            """Close the instance by closing the mutex."""
-            if self._isSingle:
-                if self._lockFile:
-                    self._lockFile.close()
-                    self._lockFile = None
-                    try:
-                        os.remove(self._lockName)
-                    except:
-                        pass
-                    try:
-                        os.remove(self._socketName)
-                    except:
-                        pass
-                
-
-        def _startSocketServer(self, raiseCallback):
-            """Start the pipe server"""
-            pipeServer = _SocketServer(self._socketName, raiseCallback)
-            pipeServer.start()
-
-        def _notifySingleton(self):
-            """Notify the already running instance of the program of our
-            presence."""
-            s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
-            for i in range(0, 3):
-                try:
-                    s.connect(self._socketName)
-                    s.send("hello")
-                    s.close()
-                    return
-                except Exception, e:
-                    print "singleton.SingleInstance._notifySingleton: failed:",
-                    print utf2unicode(str(e))
-                    time.sleep(0.5)
-
-        def __nonzero__(self):
-            """Return a boolean representation of the object.
-
-            It is True, if this is the single instance of the program."""
-            return self._isSingle            
-
-        def __del__(self):
-            """Destroy the object."""
-            self.close()
-                
-#----------------------------------------------------------------------------
-#----------------------------------------------------------------------------
-
-# MAVA Logger X-specific stuff
-
-#----------------------------------------------------------------------------
-
-# The callback to use
-raiseCallback = None
-
-#----------------------------------------------------------------------------
-
-def raiseCallbackWrapper():
-    """The actual function to be used as the callback.
-
-    It checks if raiseCallback is None, and if not, it calls that."""
-    callback = raiseCallback
-    if callback is not None:
-        callback()
-
-#----------------------------------------------------------------------------
-#----------------------------------------------------------------------------
-
-if __name__=="__main__":
-    def raiseCallback():
-        print "Raise the window!"
-
-    instance = SingleInstance("mlx", raiseCallback)
-    if instance:
-        print "The first instance"
-        time.sleep(10)
-    else:
-        print "The program is already running."
-
Index: src/mlx/sound.py
===================================================================
--- src/mlx/sound.py	(revision 585)
+++ src/mlx/sound.py	(revision 585)
@@ -0,0 +1,265 @@
+
+from util import utf2unicode
+
+import os
+import traceback
+
+#------------------------------------------------------------------------------
+
+## @package mlx.sound
+#
+# Sound playback handling.
+#
+# This is the low level sound playback handling. The \ref initializeSound
+# function should be called to initialize the sound handling with the directory
+# containing the sound files. Then the \ref startSound function should be
+# called to start the playback of a certain sound file. A callback may be
+# called when the playback of a certain file has finished.
+#
+# See also the \ref mlx.soundsched module.
+
+#------------------------------------------------------------------------------
+
+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._requests = []
+            self._pending = []
+            self._count = 0
+
+            self.daemon = True
+
+        def requestSound(self, name, finishCallback = None, extra = None):
+            """Request the playback of the sound with the given name."""
+            path = name if os.path.isabs(name) \
+                   else os.path.join(self._soundsDirectory, name)
+            with self._requestCondition:
+                self._requests.append((path, (finishCallback, extra)))
+                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._requests:
+                        if self._pending:
+                            timeout = max(self._pending[0][0] - time.time(),
+                                          0.0)
+                        else:
+                            timeout = 10.0
+
+                        #print "Waiting", timeout
+                        self._requestCondition.wait(timeout)
+
+                    requests = []
+                    for (path, finishData) in self._requests:
+                        requests.append((path, finishData, self._count))
+                        self._count += 1
+                    self._requests = []
+
+                    now = time.time()
+                    toClose = []
+                    while self._pending and \
+                          self._pending[0][0]<=now:
+                        toClose.append(self._pending[0][1])
+                        del self._pending[0]
+
+                for (alias, (finishCallback, extra)) in toClose:
+                    success = True
+                    try:
+                        print "Closing", alias
+                        self._mci.send("close " + alias)
+                        print "Closed", alias
+                    except Exception, e:
+                        print "Failed closing " + alias + ":",
+                        print utf2unicode(str(e))
+                        success = False
+
+                    if finishCallback is not None:
+                        try:
+                            finishCallback(success, extra)
+                        except:
+                            traceback.print_exc()
+
+                for (path, finishData, counter) in requests:
+                    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._pending.append((timeout, (alias, finishData)))
+                            self._pending.sort()
+                        print "Started to play", path
+                    except Exception, e:
+                        print "Failed to start playing " + path + ":",
+                        print utf2unicode(str(e))
+                        (finishCallback, extra) = finishData
+                        if finishCallback is not None:
+                            try:
+                                finishCallback(None, extra)
+                            except:
+                                traceback.print_exc()
+
+    _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, finishCallback = None, extra = None):
+        """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, finishCallback = finishCallback,
+                             extra = extra)
+
+    def finalizeSound():
+        """Finalize the sound handling."""
+        pass
+
+#------------------------------------------------------------------------------
+
+else: # os.name!="nt"
+    import threading
+    import time
+    try:
+        from common import gst_init, gobject, gst_element_factory_make
+        from common import GST_STATE_PLAYING, GST_MESSAGE_EOS
+
+        _soundsDirectory = None
+        _bins = set()
+
+        def initializeSound(soundsDirectory):
+            """Initialize the sound handling with the given directory containing
+            the sound files."""
+            gst_init()
+            global _soundsDirectory
+            _soundsDirectory = soundsDirectory
+
+        def startSound(name, finishCallback = None, extra = None):
+            """Start playing back the given sound."""
+            try:
+                playBin = gst_element_factory_make("playbin")
+
+                bus = playBin.get_bus()
+                bus.enable_sync_message_emission()
+                bus.add_signal_watch()
+                bus.connect("message", _handlePlayBinMessage,
+                            playBin, finishCallback, extra)
+
+                path = os.path.join(_soundsDirectory, name)
+                playBin.set_property( "uri", "file://%s" % (path,))
+
+                playBin.set_state(GST_STATE_PLAYING)
+                _bins.add(playBin)
+            except Exception, e:
+                print "mlx.sound.startSound: " + str(e)
+                if finishCallback is not None:
+                    finishCallback(False, extra)
+
+        def finalizeSound():
+            """Finalize the sound handling."""
+            pass
+
+        def _handlePlayBinMessage(bus, message, bin, finishCallback, extra):
+            """Handle the end of the playback of a sound file."""
+
+            # if message.type==GST_MESSAGE_EOS:
+            #     _bins.remove(bin)
+            #     if finishCallback is not None:
+            #         finishCallback(True, extra)
+
+    except:
+        print "The Gst library is missing from your system. It is needed for sound playback on Linux"
+        traceback.print_exc()
+        def initializeSound(soundsDirectory):
+            """Initialize the sound handling with the given directory containing
+            the sound files."""
+            pass
+
+        def startSound(name, finishCallback = None, extra = None):
+            """Start playing back the given sound.
+
+            FIXME: it does not do anything currently, but it should."""
+            print "sound.startSound:", name
+
+        def finalizeSound():
+            """Finalize the sound handling."""
+            pass
+
+#------------------------------------------------------------------------------
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    def callback(result, extra):
+        print "callback", result, extra
+
+    initializeSound("e:\\home\\vi\\tmp")
+    startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
+    time.sleep(5)
+    startSound("ding.wav", finishCallback = callback, extra="ding1.wav")
+    time.sleep(5)
+    startSound("ding.wav", finishCallback = callback, extra="ding2.wav")
+    time.sleep(5)
+    startSound("ding.wav", finishCallback = callback, extra="ding3.wav")
+    time.sleep(50)
+
+#------------------------------------------------------------------------------
Index: src/mlx/sound.py
===================================================================
--- src/mlx/sound.py	(revision 585)
+++ 	(revision )
@@ -1,265 +1,0 @@
-
-from util import utf2unicode
-
-import os
-import traceback
-
-#------------------------------------------------------------------------------
-
-## @package mlx.sound
-#
-# Sound playback handling.
-#
-# This is the low level sound playback handling. The \ref initializeSound
-# function should be called to initialize the sound handling with the directory
-# containing the sound files. Then the \ref startSound function should be
-# called to start the playback of a certain sound file. A callback may be
-# called when the playback of a certain file has finished.
-#
-# See also the \ref mlx.soundsched module.
-
-#------------------------------------------------------------------------------
-
-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._requests = []
-            self._pending = []
-            self._count = 0
-
-            self.daemon = True
-
-        def requestSound(self, name, finishCallback = None, extra = None):
-            """Request the playback of the sound with the given name."""
-            path = name if os.path.isabs(name) \
-                   else os.path.join(self._soundsDirectory, name)
-            with self._requestCondition:
-                self._requests.append((path, (finishCallback, extra)))
-                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._requests:
-                        if self._pending:
-                            timeout = max(self._pending[0][0] - time.time(),
-                                          0.0)
-                        else:
-                            timeout = 10.0
-
-                        #print "Waiting", timeout
-                        self._requestCondition.wait(timeout)
-
-                    requests = []
-                    for (path, finishData) in self._requests:
-                        requests.append((path, finishData, self._count))
-                        self._count += 1
-                    self._requests = []
-
-                    now = time.time()
-                    toClose = []
-                    while self._pending and \
-                          self._pending[0][0]<=now:
-                        toClose.append(self._pending[0][1])
-                        del self._pending[0]
-
-                for (alias, (finishCallback, extra)) in toClose:
-                    success = True
-                    try:
-                        print "Closing", alias
-                        self._mci.send("close " + alias)
-                        print "Closed", alias
-                    except Exception, e:
-                        print "Failed closing " + alias + ":",
-                        print utf2unicode(str(e))
-                        success = False
-
-                    if finishCallback is not None:
-                        try:
-                            finishCallback(success, extra)
-                        except:
-                            traceback.print_exc()
-
-                for (path, finishData, counter) in requests:
-                    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._pending.append((timeout, (alias, finishData)))
-                            self._pending.sort()
-                        print "Started to play", path
-                    except Exception, e:
-                        print "Failed to start playing " + path + ":",
-                        print utf2unicode(str(e))
-                        (finishCallback, extra) = finishData
-                        if finishCallback is not None:
-                            try:
-                                finishCallback(None, extra)
-                            except:
-                                traceback.print_exc()
-
-    _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, finishCallback = None, extra = None):
-        """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, finishCallback = finishCallback,
-                             extra = extra)
-
-    def finalizeSound():
-        """Finalize the sound handling."""
-        pass
-
-#------------------------------------------------------------------------------
-
-else: # os.name!="nt"
-    import threading
-    import time
-    try:
-        from common import gst_init, gobject, gst_element_factory_make
-        from common import GST_STATE_PLAYING, GST_MESSAGE_EOS
-
-        _soundsDirectory = None
-        _bins = set()
-
-        def initializeSound(soundsDirectory):
-            """Initialize the sound handling with the given directory containing
-            the sound files."""
-            gst_init()
-            global _soundsDirectory
-            _soundsDirectory = soundsDirectory
-
-        def startSound(name, finishCallback = None, extra = None):
-            """Start playing back the given sound."""
-            try:
-                playBin = gst_element_factory_make("playbin")
-
-                bus = playBin.get_bus()
-                bus.enable_sync_message_emission()
-                bus.add_signal_watch()
-                bus.connect("message", _handlePlayBinMessage,
-                            playBin, finishCallback, extra)
-
-                path = os.path.join(_soundsDirectory, name)
-                playBin.set_property( "uri", "file://%s" % (path,))
-
-                playBin.set_state(GST_STATE_PLAYING)
-                _bins.add(playBin)
-            except Exception, e:
-                print "mlx.sound.startSound: " + str(e)
-                if finishCallback is not None:
-                    finishCallback(False, extra)
-
-        def finalizeSound():
-            """Finalize the sound handling."""
-            pass
-
-        def _handlePlayBinMessage(bus, message, bin, finishCallback, extra):
-            """Handle the end of the playback of a sound file."""
-
-            # if message.type==GST_MESSAGE_EOS:
-            #     _bins.remove(bin)
-            #     if finishCallback is not None:
-            #         finishCallback(True, extra)
-
-    except:
-        print "The Gst library is missing from your system. It is needed for sound playback on Linux"
-        traceback.print_exc()
-        def initializeSound(soundsDirectory):
-            """Initialize the sound handling with the given directory containing
-            the sound files."""
-            pass
-
-        def startSound(name, finishCallback = None, extra = None):
-            """Start playing back the given sound.
-
-            FIXME: it does not do anything currently, but it should."""
-            print "sound.startSound:", name
-
-        def finalizeSound():
-            """Finalize the sound handling."""
-            pass
-
-#------------------------------------------------------------------------------
-#------------------------------------------------------------------------------
-
-if __name__ == "__main__":
-    def callback(result, extra):
-        print "callback", result, extra
-
-    initializeSound("e:\\home\\vi\\tmp")
-    startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
-    time.sleep(5)
-    startSound("ding.wav", finishCallback = callback, extra="ding1.wav")
-    time.sleep(5)
-    startSound("ding.wav", finishCallback = callback, extra="ding2.wav")
-    time.sleep(5)
-    startSound("ding.wav", finishCallback = callback, extra="ding3.wav")
-    time.sleep(50)
-
-#------------------------------------------------------------------------------
Index: src/mlx/soundsched.py
===================================================================
--- src/mlx/soundsched.py	(revision 352)
+++ src/mlx/soundsched.py	(revision 352)
@@ -0,0 +1,259 @@
+
+from sound import startSound
+import const
+import fs
+
+import threading
+
+#------------------------------------------------------------------------------
+
+## @package mlx.soundsched
+#
+# Sound playback scheduling.
+#
+# The logger may play certain sound files when certain conditions hold. For
+# example, if the landing or the strobe lights are switched on during taxi, and
+# we go to the takeoff state, a voice says "Cabin crew, please take your seats
+# for takeoff". Some sounds are played only when the pilot presses a hotkey, if
+# the program is set up that way.
+#
+# Each sound is represented by an instance of a subclass of \ref Sound. If a
+# new \ref mlx.fs.AircraftState "aircraft state" comes in from the simulator,
+# the \ref Sound.shouldPlay "shouldPlay" functions of the registered sound
+# objects are called with that state, and if the function returns \c True, the
+# corresponding sound file is played.
+#
+# The \ref ChecklistScheduler class handles the playback of the checklists,
+# which are basically sequences of sound files played back one-by-one when a
+# certain hotkey is pressed.
+
+#------------------------------------------------------------------------------
+
+class Sound(object):
+    """A sound (file) that should be played in certain circumstances."""
+
+    # Common lock for sounds
+    _lock = threading.Lock()
+
+    def __init__(self, name):
+        """Construct the sound object for the sound file with the given
+        name."""
+        self._name = name
+        
+        self._playing = 0
+
+    @property
+    def playing(self):
+        """Determine if sound is playing or not."""
+        return self._playing>0
+
+    def check(self, flight, state, pilotHotkeyPressed):
+        """Check if the file should be played, and if it should be, play it."""
+        if self.shouldPlay(flight, state, pilotHotkeyPressed):
+            with Sound._lock:
+                self._playing += 1
+            startSound(self._name, finishCallback = self._playbackDone)
+
+    def shouldPlay(self, flight, state, pilotHotkeyPressed):
+        """Determine if the sound should be played.
+
+        This default implementation returns False."""
+        return False
+
+    def _playbackDone(self, success, extra):
+        """Called when the playback of thee sound has finished (or failed)."""
+        if success is None:
+            print "Failed to start sound", self._name
+        elif not success:
+            print "Failed to finish sound", self._name
+        with Sound._lock:            
+            self._playing -= 1
+
+#------------------------------------------------------------------------------
+
+class ScreamSound(Sound):
+    """A screaming sound that is played under certain circumstance."""
+    def __init__(self):
+        """Construct the screaming sound."""
+        super(ScreamSound, self).__init__(const.SOUND_SCREAM)
+
+    def shouldPlay(self, flight, state, pilotHotkeyPressed):
+        """Determine if the sound should be played.
+
+        It should be played if it is not being played and the absolute value of
+        the vertical speed is greater than 6000 fpm."""
+        return not self.playing and abs(state.vs)>6000        
+
+#------------------------------------------------------------------------------
+
+class SimpleSound(Sound):
+    """A simple sound that should be played only once, in a certain flight
+    stage when the hotkey has been pressed or it is not the pilot who controls
+    the sounds.
+
+    If it is not the pilot who controls the sounds, it may be delayed relative
+    to the first detection of a certain flight stage."""
+    def __init__(self, name, stage, delay = 0.0, extraCondition = None,
+                 considerHotkey = True, previousSound = None):
+        """Construct the simple sound."""
+        super(SimpleSound, self).__init__(name)
+        
+        self._stage = stage
+        self._delay = delay
+        self._extraCondition = extraCondition
+        self._considerHotkey = considerHotkey
+        self._previousSound = previousSound
+
+        self._played = False
+        self._stageDetectionTime = None
+
+    def shouldPlay(self, flight, state, pilotHotkeyPressed):
+        """Determine if the sound should be played."""
+        if flight.stage!=self._stage or self._played or \
+           (self._previousSound is not None and
+            self._previousSound.playing):
+            return False
+
+        toPlay = False
+        if flight.config.pilotControlsSounds and self._considerHotkey:
+            toPlay = pilotHotkeyPressed
+        else:
+            if self._stageDetectionTime is None:
+                self._stageDetectionTime = state.timestamp
+            toPlay = state.timestamp>=(self._stageDetectionTime + self._delay)
+            if toPlay and self._extraCondition is not None:
+                toPlay = self._extraCondition(flight, state)
+
+        if toPlay:
+            self._played = True
+
+        return toPlay
+
+#------------------------------------------------------------------------------
+
+class TaxiSound(SimpleSound):
+    """The taxi sound.
+
+    It first plays the Malev theme song, then an aircraft-specific taxi
+    sound. The playback is started only, if the boarding sound is not being
+    played."""
+
+    _sounds = { const.AIRCRAFT_B736  : const.SOUND_TAXI_BOEING737NG,
+                const.AIRCRAFT_B737  : const.SOUND_TAXI_BOEING737NG,
+                const.AIRCRAFT_B738  : const.SOUND_TAXI_BOEING737NG,
+                const.AIRCRAFT_B738C : const.SOUND_TAXI_BOEING737NG,
+                const.AIRCRAFT_B762  : const.SOUND_TAXI_BOEING767,
+                const.AIRCRAFT_B763  : const.SOUND_TAXI_BOEING767,
+                const.AIRCRAFT_F70   : const.SOUND_TAXI_F70 }
+
+    def __init__(self, flight, boardingSound = None):
+        """Construct the taxi sound."""
+        super(TaxiSound, self).__init__(const.SOUND_MALEV,
+                                        const.STAGE_PUSHANDTAXI,
+                                        previousSound = boardingSound,
+                                        extraCondition = lambda _flight, state:
+                                        state.groundSpeed>5)
+
+        self._flight = flight
+
+    def _playbackDone(self, success, extra):
+        """Called when the playback is done.
+
+        It starts playing the aircraft type-specific taxi sound, if any."""
+        super(TaxiSound, self)._playbackDone(success, extra)
+        aircraftType = self._flight.aircraftType
+        sounds = TaxiSound._sounds
+        if aircraftType in sounds:
+            startSound(sounds[aircraftType])
+
+#------------------------------------------------------------------------------
+
+class TouchdownApplause(Sound):
+    """An applause sound that is played after a gentle touchdown."""
+    def __init__(self):
+        super(TouchdownApplause, self).__init__(const.SOUND_APPLAUSE)
+
+        self._touchdownTime = None
+        self._played = False
+        
+    def shouldPlay(self, flight, state, pilotHotkeyPressed):
+        """Determine if the sound should be played.
+
+        It should be played if we are 2 seconds after a gentle touchdown."""
+        if self._played or flight.tdRate is None:
+            return False
+
+        if self._touchdownTime is None:
+            self._touchdownTime = state.timestamp
+
+        if state.timestamp>=(self._touchdownTime + 2) and \
+           flight.tdRate>=-150 and flight.tdRate<0:
+            self._played = True
+            return True
+        else:
+            return False
+        
+#------------------------------------------------------------------------------
+
+class SoundScheduler(object):
+    """A scheduler for the sounds."""
+    def __init__(self, flight):
+        """Construct the sound scheduler for the given flight."""
+        self._flight = flight
+        self._sounds = []
+
+        self._sounds.append(ScreamSound())
+
+        boardingSound = SimpleSound(const.SOUND_BOARDING,
+                                    const.STAGE_BOARDING,
+                                    delay = 10.0)
+        self._sounds.append(boardingSound)
+        self._sounds.append(TaxiSound(flight, boardingSound))
+        self._sounds.append(SimpleSound(const.SOUND_CAPTAIN_TAKEOFF,
+                                        const.STAGE_TAKEOFF,
+                                        extraCondition = lambda flight, state:
+                                        state.onTheGround,
+                                        considerHotkey = False))
+        self._sounds.append(SimpleSound(const.SOUND_CRUISE, const.STAGE_CRUISE))
+        self._sounds.append(SimpleSound(const.SOUND_DESCENT, const.STAGE_DESCENT,
+                                        extraCondition = lambda flight, state:
+                                        state.altitude<15000))
+        self._sounds.append(TouchdownApplause())
+        self._sounds.append(SimpleSound(const.SOUND_TAXIAFTERLAND,
+                                        const.STAGE_TAXIAFTERLAND,
+                                        delay = 10.0))
+
+    def schedule(self, state, pilotHotkeyPressed):
+        """Schedule any sound, if needed."""
+        flight = self._flight
+        if flight.config.enableSounds:
+            for sound in self._sounds:
+                sound.check(flight, state, pilotHotkeyPressed)
+
+#------------------------------------------------------------------------------
+
+class ChecklistScheduler(object):
+    """A scheduler for the checklist sounds"""
+    def __init__(self, flight):
+        """Construct the checklist scheduler for the given flight."""
+        self._flight = flight
+        self._checklist = None
+        self._itemIndex = 0
+
+    def hotkeyPressed(self):
+        """Called when the checklist hotkey is pressed."""
+        flight = self._flight
+        config = flight.config
+        if config.enableChecklists and flight.aircraftType is not None:
+            if self._checklist is None:
+                self._checklist = config.getChecklist(flight.aircraftType)
+
+            index = self._itemIndex
+            if index>=len(self._checklist):
+                fs.sendMessage(const.MESSAGETYPE_INFORMATION,
+                               "End of checklist")
+            else:
+                startSound(self._checklist[index])
+                self._itemIndex += 1
+
+#------------------------------------------------------------------------------
Index: src/mlx/soundsched.py
===================================================================
--- src/mlx/soundsched.py	(revision 352)
+++ 	(revision )
@@ -1,259 +1,0 @@
-
-from sound import startSound
-import const
-import fs
-
-import threading
-
-#------------------------------------------------------------------------------
-
-## @package mlx.soundsched
-#
-# Sound playback scheduling.
-#
-# The logger may play certain sound files when certain conditions hold. For
-# example, if the landing or the strobe lights are switched on during taxi, and
-# we go to the takeoff state, a voice says "Cabin crew, please take your seats
-# for takeoff". Some sounds are played only when the pilot presses a hotkey, if
-# the program is set up that way.
-#
-# Each sound is represented by an instance of a subclass of \ref Sound. If a
-# new \ref mlx.fs.AircraftState "aircraft state" comes in from the simulator,
-# the \ref Sound.shouldPlay "shouldPlay" functions of the registered sound
-# objects are called with that state, and if the function returns \c True, the
-# corresponding sound file is played.
-#
-# The \ref ChecklistScheduler class handles the playback of the checklists,
-# which are basically sequences of sound files played back one-by-one when a
-# certain hotkey is pressed.
-
-#------------------------------------------------------------------------------
-
-class Sound(object):
-    """A sound (file) that should be played in certain circumstances."""
-
-    # Common lock for sounds
-    _lock = threading.Lock()
-
-    def __init__(self, name):
-        """Construct the sound object for the sound file with the given
-        name."""
-        self._name = name
-        
-        self._playing = 0
-
-    @property
-    def playing(self):
-        """Determine if sound is playing or not."""
-        return self._playing>0
-
-    def check(self, flight, state, pilotHotkeyPressed):
-        """Check if the file should be played, and if it should be, play it."""
-        if self.shouldPlay(flight, state, pilotHotkeyPressed):
-            with Sound._lock:
-                self._playing += 1
-            startSound(self._name, finishCallback = self._playbackDone)
-
-    def shouldPlay(self, flight, state, pilotHotkeyPressed):
-        """Determine if the sound should be played.
-
-        This default implementation returns False."""
-        return False
-
-    def _playbackDone(self, success, extra):
-        """Called when the playback of thee sound has finished (or failed)."""
-        if success is None:
-            print "Failed to start sound", self._name
-        elif not success:
-            print "Failed to finish sound", self._name
-        with Sound._lock:            
-            self._playing -= 1
-
-#------------------------------------------------------------------------------
-
-class ScreamSound(Sound):
-    """A screaming sound that is played under certain circumstance."""
-    def __init__(self):
-        """Construct the screaming sound."""
-        super(ScreamSound, self).__init__(const.SOUND_SCREAM)
-
-    def shouldPlay(self, flight, state, pilotHotkeyPressed):
-        """Determine if the sound should be played.
-
-        It should be played if it is not being played and the absolute value of
-        the vertical speed is greater than 6000 fpm."""
-        return not self.playing and abs(state.vs)>6000        
-
-#------------------------------------------------------------------------------
-
-class SimpleSound(Sound):
-    """A simple sound that should be played only once, in a certain flight
-    stage when the hotkey has been pressed or it is not the pilot who controls
-    the sounds.
-
-    If it is not the pilot who controls the sounds, it may be delayed relative
-    to the first detection of a certain flight stage."""
-    def __init__(self, name, stage, delay = 0.0, extraCondition = None,
-                 considerHotkey = True, previousSound = None):
-        """Construct the simple sound."""
-        super(SimpleSound, self).__init__(name)
-        
-        self._stage = stage
-        self._delay = delay
-        self._extraCondition = extraCondition
-        self._considerHotkey = considerHotkey
-        self._previousSound = previousSound
-
-        self._played = False
-        self._stageDetectionTime = None
-
-    def shouldPlay(self, flight, state, pilotHotkeyPressed):
-        """Determine if the sound should be played."""
-        if flight.stage!=self._stage or self._played or \
-           (self._previousSound is not None and
-            self._previousSound.playing):
-            return False
-
-        toPlay = False
-        if flight.config.pilotControlsSounds and self._considerHotkey:
-            toPlay = pilotHotkeyPressed
-        else:
-            if self._stageDetectionTime is None:
-                self._stageDetectionTime = state.timestamp
-            toPlay = state.timestamp>=(self._stageDetectionTime + self._delay)
-            if toPlay and self._extraCondition is not None:
-                toPlay = self._extraCondition(flight, state)
-
-        if toPlay:
-            self._played = True
-
-        return toPlay
-
-#------------------------------------------------------------------------------
-
-class TaxiSound(SimpleSound):
-    """The taxi sound.
-
-    It first plays the Malev theme song, then an aircraft-specific taxi
-    sound. The playback is started only, if the boarding sound is not being
-    played."""
-
-    _sounds = { const.AIRCRAFT_B736  : const.SOUND_TAXI_BOEING737NG,
-                const.AIRCRAFT_B737  : const.SOUND_TAXI_BOEING737NG,
-                const.AIRCRAFT_B738  : const.SOUND_TAXI_BOEING737NG,
-                const.AIRCRAFT_B738C : const.SOUND_TAXI_BOEING737NG,
-                const.AIRCRAFT_B762  : const.SOUND_TAXI_BOEING767,
-                const.AIRCRAFT_B763  : const.SOUND_TAXI_BOEING767,
-                const.AIRCRAFT_F70   : const.SOUND_TAXI_F70 }
-
-    def __init__(self, flight, boardingSound = None):
-        """Construct the taxi sound."""
-        super(TaxiSound, self).__init__(const.SOUND_MALEV,
-                                        const.STAGE_PUSHANDTAXI,
-                                        previousSound = boardingSound,
-                                        extraCondition = lambda _flight, state:
-                                        state.groundSpeed>5)
-
-        self._flight = flight
-
-    def _playbackDone(self, success, extra):
-        """Called when the playback is done.
-
-        It starts playing the aircraft type-specific taxi sound, if any."""
-        super(TaxiSound, self)._playbackDone(success, extra)
-        aircraftType = self._flight.aircraftType
-        sounds = TaxiSound._sounds
-        if aircraftType in sounds:
-            startSound(sounds[aircraftType])
-
-#------------------------------------------------------------------------------
-
-class TouchdownApplause(Sound):
-    """An applause sound that is played after a gentle touchdown."""
-    def __init__(self):
-        super(TouchdownApplause, self).__init__(const.SOUND_APPLAUSE)
-
-        self._touchdownTime = None
-        self._played = False
-        
-    def shouldPlay(self, flight, state, pilotHotkeyPressed):
-        """Determine if the sound should be played.
-
-        It should be played if we are 2 seconds after a gentle touchdown."""
-        if self._played or flight.tdRate is None:
-            return False
-
-        if self._touchdownTime is None:
-            self._touchdownTime = state.timestamp
-
-        if state.timestamp>=(self._touchdownTime + 2) and \
-           flight.tdRate>=-150 and flight.tdRate<0:
-            self._played = True
-            return True
-        else:
-            return False
-        
-#------------------------------------------------------------------------------
-
-class SoundScheduler(object):
-    """A scheduler for the sounds."""
-    def __init__(self, flight):
-        """Construct the sound scheduler for the given flight."""
-        self._flight = flight
-        self._sounds = []
-
-        self._sounds.append(ScreamSound())
-
-        boardingSound = SimpleSound(const.SOUND_BOARDING,
-                                    const.STAGE_BOARDING,
-                                    delay = 10.0)
-        self._sounds.append(boardingSound)
-        self._sounds.append(TaxiSound(flight, boardingSound))
-        self._sounds.append(SimpleSound(const.SOUND_CAPTAIN_TAKEOFF,
-                                        const.STAGE_TAKEOFF,
-                                        extraCondition = lambda flight, state:
-                                        state.onTheGround,
-                                        considerHotkey = False))
-        self._sounds.append(SimpleSound(const.SOUND_CRUISE, const.STAGE_CRUISE))
-        self._sounds.append(SimpleSound(const.SOUND_DESCENT, const.STAGE_DESCENT,
-                                        extraCondition = lambda flight, state:
-                                        state.altitude<15000))
-        self._sounds.append(TouchdownApplause())
-        self._sounds.append(SimpleSound(const.SOUND_TAXIAFTERLAND,
-                                        const.STAGE_TAXIAFTERLAND,
-                                        delay = 10.0))
-
-    def schedule(self, state, pilotHotkeyPressed):
-        """Schedule any sound, if needed."""
-        flight = self._flight
-        if flight.config.enableSounds:
-            for sound in self._sounds:
-                sound.check(flight, state, pilotHotkeyPressed)
-
-#------------------------------------------------------------------------------
-
-class ChecklistScheduler(object):
-    """A scheduler for the checklist sounds"""
-    def __init__(self, flight):
-        """Construct the checklist scheduler for the given flight."""
-        self._flight = flight
-        self._checklist = None
-        self._itemIndex = 0
-
-    def hotkeyPressed(self):
-        """Called when the checklist hotkey is pressed."""
-        flight = self._flight
-        config = flight.config
-        if config.enableChecklists and flight.aircraftType is not None:
-            if self._checklist is None:
-                self._checklist = config.getChecklist(flight.aircraftType)
-
-            index = self._itemIndex
-            if index>=len(self._checklist):
-                fs.sendMessage(const.MESSAGETYPE_INFORMATION,
-                               "End of checklist")
-            else:
-                startSound(self._checklist[index])
-                self._itemIndex += 1
-
-#------------------------------------------------------------------------------
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 681)
+++ src/mlx/update.py	(revision 681)
@@ -0,0 +1,615 @@
+
+from config import Config
+from util import utf2unicode
+
+import os
+import sys
+import urllib2
+import tempfile
+import socket
+import subprocess
+import hashlib
+
+if os.name=="nt":
+    import win32api
+
+#------------------------------------------------------------------------------
+
+## @package mlx.update
+#
+# Automatic update handling.
+#
+# The program can update itself automatically. For this purpose it maintains a
+# manifest of the files installed containing the relative paths, sizes and
+# checksums of each files. When the program starts up, this manifest file is
+# downloaded from the update server and is compared to the local one. Then the
+# updated and new files are downloaded with names that are created by appending
+# the checksum to the actual name, so as not to overwrite any existing files at
+# this stage. If all files are downloaded, the downloaded files are renamed to
+# their real names. On Windows, the old file is removed first to avoid trouble
+# with 'busy' files. If removing a file fails, the file will be moved to a
+# temporary directory, that will be removed when the program starts the next
+# time.
+
+#------------------------------------------------------------------------------
+
+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 copy(self):
+        """Create a copy of the manifest."""
+        manifest = Manifest()
+        manifest._files = self._files.copy()
+        return manifest
+        
+    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)
+
+    def __contains__(self, path):
+        """Determine if the given path is in the manifest."""
+        return path in self._files
+
+    def __getitem__(self, path):
+        """Get data of the file with the given path."""
+        return self._files[path] if path in self._files else None
+            
+#------------------------------------------------------------------------------
+
+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:", utf2unicode(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:
+        error = utf2unicode(str(e))
+        print >> sys.stderr, "Error downloading manifest:", error
+        listener.failed(error)
+        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 + ": " + utf2unicode(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:
+        error = utf2unicode(str(e))
+        print >> sys.stderr, "Error:", error
+        listener.failed(error)
+
+#------------------------------------------------------------------------------
+
+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, utf2unicode(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 + " " + updateURL, "", 1)
+        else:
+            process = subprocess.Popen([os.path.join(directory, "mlxupdate"),
+                                        str(port), manifestFile, updateURL],
+                                        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:
+        error = utf2unicode(str(e))
+        print >> sys.stderr, "Failed updating:", error
+        listener.failed(error)
+    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])))
+
+    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, sys.argv[3],
+                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 681)
+++ 	(revision )
@@ -1,615 +1,0 @@
-
-from config import Config
-from util import utf2unicode
-
-import os
-import sys
-import urllib2
-import tempfile
-import socket
-import subprocess
-import hashlib
-
-if os.name=="nt":
-    import win32api
-
-#------------------------------------------------------------------------------
-
-## @package mlx.update
-#
-# Automatic update handling.
-#
-# The program can update itself automatically. For this purpose it maintains a
-# manifest of the files installed containing the relative paths, sizes and
-# checksums of each files. When the program starts up, this manifest file is
-# downloaded from the update server and is compared to the local one. Then the
-# updated and new files are downloaded with names that are created by appending
-# the checksum to the actual name, so as not to overwrite any existing files at
-# this stage. If all files are downloaded, the downloaded files are renamed to
-# their real names. On Windows, the old file is removed first to avoid trouble
-# with 'busy' files. If removing a file fails, the file will be moved to a
-# temporary directory, that will be removed when the program starts the next
-# time.
-
-#------------------------------------------------------------------------------
-
-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 copy(self):
-        """Create a copy of the manifest."""
-        manifest = Manifest()
-        manifest._files = self._files.copy()
-        return manifest
-        
-    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)
-
-    def __contains__(self, path):
-        """Determine if the given path is in the manifest."""
-        return path in self._files
-
-    def __getitem__(self, path):
-        """Get data of the file with the given path."""
-        return self._files[path] if path in self._files else None
-            
-#------------------------------------------------------------------------------
-
-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:", utf2unicode(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:
-        error = utf2unicode(str(e))
-        print >> sys.stderr, "Error downloading manifest:", error
-        listener.failed(error)
-        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 + ": " + utf2unicode(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:
-        error = utf2unicode(str(e))
-        print >> sys.stderr, "Error:", error
-        listener.failed(error)
-
-#------------------------------------------------------------------------------
-
-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, utf2unicode(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 + " " + updateURL, "", 1)
-        else:
-            process = subprocess.Popen([os.path.join(directory, "mlxupdate"),
-                                        str(port), manifestFile, updateURL],
-                                        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:
-        error = utf2unicode(str(e))
-        print >> sys.stderr, "Failed updating:", error
-        listener.failed(error)
-    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])))
-
-    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, sys.argv[3],
-                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 401)
+++ src/mlx/util.py	(revision 401)
@@ -0,0 +1,175 @@
+
+import math
+import time
+import sys
+import codecs
+
+#------------------------------------------------------------------------------
+
+## @package mlx.util
+#
+# Utilities.
+
+#------------------------------------------------------------------------------
+
+## 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,)
+
+#------------------------------------------------------------------------------
+
+secondaryInstallation="secondary" in sys.argv
+
+#------------------------------------------------------------------------------
+
+_utf8decoder = codecs.getdecoder("utf-8")
+
+def utf2unicode(text):
+    """Convert the given text from UTF-8 encoding to unicode."""
+    return unicode(_utf8decoder(text)[0])
Index: src/mlx/util.py
===================================================================
--- src/mlx/util.py	(revision 401)
+++ 	(revision )
@@ -1,175 +1,0 @@
-
-import math
-import time
-import sys
-import codecs
-
-#------------------------------------------------------------------------------
-
-## @package mlx.util
-#
-# Utilities.
-
-#------------------------------------------------------------------------------
-
-## 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,)
-
-#------------------------------------------------------------------------------
-
-secondaryInstallation="secondary" in sys.argv
-
-#------------------------------------------------------------------------------
-
-_utf8decoder = codecs.getdecoder("utf-8")
-
-def utf2unicode(text):
-    """Convert the given text from UTF-8 encoding to unicode."""
-    return unicode(_utf8decoder(text)[0])
Index: src/mlx/watchdog.py
===================================================================
--- src/mlx/watchdog.py	(revision 535)
+++ src/mlx/watchdog.py	(revision 535)
@@ -0,0 +1,136 @@
+# Watchdog
+
+#-----------------------------------------------------------------------------
+
+from threading import Thread, Lock
+
+import time
+
+#-----------------------------------------------------------------------------
+
+## @package mlx.watchdog
+#
+# Watchdog module. It implements a thread which wakes up regularly and checks
+# that none of its clients is left in a state for too long a time. If so, that
+# fact is logged.
+
+#-----------------------------------------------------------------------------
+
+class _ClientState(object):
+    """A client state.
+
+    It can be set or cleared. If set, there is a timeout associated with it. If
+    the timeout is over, the client state is logged. If it becomes clear later,
+    that fact is logged too."""
+    def __init__(self, timeout, name):
+        """Construct the client state with the given timeout and log text."""
+
+        self._lock = Lock()
+
+        self._timeout = timeout
+        self._name = name
+        self._nextTimeout = None
+        self._timedout = False
+
+    def set(self):
+        """Put the client into the set state."""
+        with self._lock:
+            if self._nextTimeout is None:
+                self._nextTimeout = time.time() + self._timeout
+
+    def clear(self):
+        """Put the client into the cleared state."""
+        with self._lock:
+            self._nextTimeout = None
+
+    def _check(self, t):
+        """Check the client state.
+
+        If it has timed out, and it has not been logged yet, it is logged. If
+        it is cleared, but had a timeout earlier, that fact is also logged."""
+        logTimeout = False
+        logCleared = False
+        with self._lock:
+            if self._nextTimeout is None:
+                logCleared = self._timedout
+                self._timedout = False
+            elif t>=self._nextTimeout:
+                logTimeout = not self._timedout
+                self._timedout = True
+
+        if logTimeout:
+            print "Watchdog client %s has timed out!" % (self._name)
+        elif logCleared:
+            print "Watchdog client %s has been cleared." % (self._name)
+
+#-----------------------------------------------------------------------------
+
+class Watchdog(Thread):
+    """The watchdog thread."""
+    _instance = None
+
+    WAKEUP_INTERVAL = 1.0
+
+    LOG_INTERVAL = 60.0
+
+    @staticmethod
+    def get():
+        """Get the only instance of the watchdog."""
+        return Watchdog._instance
+
+    def __init__(self):
+        """Construct the watchdog."""
+        assert self._instance is None
+
+        super(Watchdog, self).__init__()
+        self.daemon = True
+
+        self._lock = Lock()
+        self._clients = []
+
+        Watchdog._instance = self
+
+    def addClient(self, timeout, name):
+        """Add a client with the given timeout and name.
+
+        The new client is returned."""
+        client = _ClientState(timeout, name)
+        with self._lock:
+            self._clients.append(client)
+        return client
+
+    def run(self):
+        """Perform the client checks, then wait for WAKEUP_INTERVAL.
+
+        If LOG_INTERVAL elapses, put an entry in the debug log to confirm that
+        the watchdog still works."""
+
+        nextLogTime = time.time()
+        nextWakeupTime = nextLogTime + self.WAKEUP_INTERVAL
+
+        while True:
+            t = time.time()
+            while t>=nextWakeupTime:
+                nextWakeupTime += self.WAKEUP_INTERVAL
+
+            if t>=nextLogTime:
+                print "Watchdog.run: running"
+                while t>=nextLogTime:
+                    nextLogTime += self.LOG_INTERVAL
+
+            self._checkClients(t)
+
+            t = time.time()
+            if t<nextWakeupTime:
+                time.sleep(nextWakeupTime - t)
+
+    def _checkClients(self, t):
+        """Check the clients."""
+        with self._lock:
+            clients = self._clients[:]
+
+        for client in clients:
+            client._check(t)
+
+#-----------------------------------------------------------------------------
+#-----------------------------------------------------------------------------
Index: src/mlx/watchdog.py
===================================================================
--- src/mlx/watchdog.py	(revision 535)
+++ 	(revision )
@@ -1,136 +1,0 @@
-# Watchdog
-
-#-----------------------------------------------------------------------------
-
-from threading import Thread, Lock
-
-import time
-
-#-----------------------------------------------------------------------------
-
-## @package mlx.watchdog
-#
-# Watchdog module. It implements a thread which wakes up regularly and checks
-# that none of its clients is left in a state for too long a time. If so, that
-# fact is logged.
-
-#-----------------------------------------------------------------------------
-
-class _ClientState(object):
-    """A client state.
-
-    It can be set or cleared. If set, there is a timeout associated with it. If
-    the timeout is over, the client state is logged. If it becomes clear later,
-    that fact is logged too."""
-    def __init__(self, timeout, name):
-        """Construct the client state with the given timeout and log text."""
-
-        self._lock = Lock()
-
-        self._timeout = timeout
-        self._name = name
-        self._nextTimeout = None
-        self._timedout = False
-
-    def set(self):
-        """Put the client into the set state."""
-        with self._lock:
-            if self._nextTimeout is None:
-                self._nextTimeout = time.time() + self._timeout
-
-    def clear(self):
-        """Put the client into the cleared state."""
-        with self._lock:
-            self._nextTimeout = None
-
-    def _check(self, t):
-        """Check the client state.
-
-        If it has timed out, and it has not been logged yet, it is logged. If
-        it is cleared, but had a timeout earlier, that fact is also logged."""
-        logTimeout = False
-        logCleared = False
-        with self._lock:
-            if self._nextTimeout is None:
-                logCleared = self._timedout
-                self._timedout = False
-            elif t>=self._nextTimeout:
-                logTimeout = not self._timedout
-                self._timedout = True
-
-        if logTimeout:
-            print "Watchdog client %s has timed out!" % (self._name)
-        elif logCleared:
-            print "Watchdog client %s has been cleared." % (self._name)
-
-#-----------------------------------------------------------------------------
-
-class Watchdog(Thread):
-    """The watchdog thread."""
-    _instance = None
-
-    WAKEUP_INTERVAL = 1.0
-
-    LOG_INTERVAL = 60.0
-
-    @staticmethod
-    def get():
-        """Get the only instance of the watchdog."""
-        return Watchdog._instance
-
-    def __init__(self):
-        """Construct the watchdog."""
-        assert self._instance is None
-
-        super(Watchdog, self).__init__()
-        self.daemon = True
-
-        self._lock = Lock()
-        self._clients = []
-
-        Watchdog._instance = self
-
-    def addClient(self, timeout, name):
-        """Add a client with the given timeout and name.
-
-        The new client is returned."""
-        client = _ClientState(timeout, name)
-        with self._lock:
-            self._clients.append(client)
-        return client
-
-    def run(self):
-        """Perform the client checks, then wait for WAKEUP_INTERVAL.
-
-        If LOG_INTERVAL elapses, put an entry in the debug log to confirm that
-        the watchdog still works."""
-
-        nextLogTime = time.time()
-        nextWakeupTime = nextLogTime + self.WAKEUP_INTERVAL
-
-        while True:
-            t = time.time()
-            while t>=nextWakeupTime:
-                nextWakeupTime += self.WAKEUP_INTERVAL
-
-            if t>=nextLogTime:
-                print "Watchdog.run: running"
-                while t>=nextLogTime:
-                    nextLogTime += self.LOG_INTERVAL
-
-            self._checkClients(t)
-
-            t = time.time()
-            if t<nextWakeupTime:
-                time.sleep(nextWakeupTime - t)
-
-    def _checkClients(self, t):
-        """Check the clients."""
-        with self._lock:
-            clients = self._clients[:]
-
-        for client in clients:
-            client._check(t)
-
-#-----------------------------------------------------------------------------
-#-----------------------------------------------------------------------------
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 570)
+++ src/mlx/web.py	(revision 570)
@@ -0,0 +1,1149 @@
+
+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
+import xmlrpclib
+import HTMLParser
+
+#---------------------------------------------------------------------------------------
+
+## @package mlx.web
+#
+# Web interface.
+#
+# This module implements a thread that can perform (HTTP) requests
+# asynchronously. When the request is performed, a callback is called. The main
+# interface is the \ref Handler class. Each of its functions creates a \ref
+# Request subclass instance and puts it to the request queue. The handler
+# thread then takes the requests one by one, and executes them.
+#
+# This module also defines some data classes the contents of which are
+# retrieved or sent via HTTP. \ref BookedFlight contains data of a flight
+# booked on the MAVA website, \ref Fleet and \ref Plane represents the MAVA
+# fleet and the gates at Ferihegy and \ref NOTAM is a NOTAM.
+
+#---------------------------------------------------------------------------------------
+
+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,
+                      "73H"  : const.AIRCRAFT_B738C,
+                      "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,
+                      "146"  : const.AIRCRAFT_B462 }
+
+    TYPE2TYPECODE = { const.AIRCRAFT_B736  : "736",
+                      const.AIRCRAFT_B737  : "73G",
+                      const.AIRCRAFT_B738  : "738",
+                      const.AIRCRAFT_B738C : "73H",
+                      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",
+                      const.AIRCRAFT_B462  : "146" }
+
+    @staticmethod
+    def getDateTime(date, time):
+        """Get a datetime object from the given textual date and time."""
+        return datetime.datetime.strptime(date + " " + time,
+                                          "%Y-%m-%d %H:%M:%S")
+
+    def __init__(self, id = None):
+        """Construct a booked flight with the given ID."""
+        self.id = id
+
+    def readFromWeb(self, f):
+        """Read the data of the flight from the web via the given file
+        object."""
+        self.callsign = readline(f)
+
+        date = readline(f)
+        print "web.BookedFlight.readFromWeb: date:", date
+        if date=="0000-00-00": date = "0001-01-01"
+
+        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 = BookedFlight.getDateTime(date, departureTime)
+
+        arrivalTime = readline(f)
+        self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
+        if self.arrivalTime<self.departureTime:
+            self.arrivalTime += datetime.timedelta(days = 1)
+
+        if not readline(f)==".NEXT.":
+            raise Exception("Invalid line in flight data")
+
+    def readFromFile(self, f):
+        """Read the data of the flight from a file via the given file
+        object."""
+        date = None
+        departureTime = None
+        arrivalTime = None
+
+        line = f.readline()
+        lineNumber = 0
+        while line:
+            lineNumber += 1
+            line = line.strip()
+
+            hashIndex = line.find("#")
+            if hashIndex>=0: line = line[:hashIndex]
+            if line:
+                equalIndex = line.find("=")
+                lineOK = equalIndex>0
+
+                if lineOK:
+                    key = line[:equalIndex].strip()
+                    value = line[equalIndex+1:].strip().replace("\:", ":")
+
+                    lineOK = key and value
+
+                if lineOK:
+                    if key=="callsign": self.callsign = value
+                    elif key=="date": date = value
+                    elif key=="dep_airport": self.departureICAO = value
+                    elif key=="dest_airport": self.arrivalICAO = value
+                    elif key=="planecode": self.aircraftType = \
+                         self._decodeAircraftType(value)
+                    elif key=="planetype": self.aircraftTypeName = value
+                    elif key=="tail_nr": self.tailNumber = value
+                    elif key=="passenger": self.numPassengers = int(value)
+                    elif key=="crew": self.numCrew = int(value)
+                    elif key=="bag": self.bagWeight = int(value)
+                    elif key=="cargo": self.cargoWeight = int(value)
+                    elif key=="mail": self.mailWeight = int(value)
+                    elif key=="flight_route": self.route = value
+                    elif key=="departure_time": departureTime = value
+                    elif key=="arrival_time": arrivalTime = value
+                    elif key=="foglalas_id":
+                        self.id = None if value=="0" else value
+                    else: lineOK = False
+
+                if not lineOK:
+                    print "web.BookedFlight.readFromFile: line %d is invalid" % \
+                          (lineNumber,)
+
+            line = f.readline()
+
+        if date is not None:
+            if departureTime is not None:
+                self.departureTime = BookedFlight.getDateTime(date,
+                                                              departureTime)
+            if arrivalTime is not None:
+                self.arrivalTime = BookedFlight.getDateTime(date,
+                                                            arrivalTime)
+
+        d = dir(self)
+        for attribute in ["callsign", "departureICAO", "arrivalICAO",
+                          "aircraftType", "tailNumber",
+                          "numPassengers", "numCrew",
+                          "bagWeight", "cargoWeight", "mailWeight",
+                          "route", "departureTime", "arrivalTime"]:
+            if attribute not in d:
+                raise Exception("Attribute %s could not be read" % (attribute,))
+
+        if "aircraftTypeName" not in d:
+            self.aircraftTypeName = \
+                BookedFlight.TYPE2TYPECODE[self.aircraftType]
+
+    def writeIntoFile(self, f):
+        """Write the flight into a file."""
+        print >> f, "callsign=%s" % (self.callsign,)
+        date = self.departureTime.date()
+        print >> f, "date=%04d-%02d-%0d" % (date.year, date.month, date.day)
+        print >> f, "dep_airport=%s" % (self.departureICAO,)
+        print >> f, "dest_airport=%s" % (self.arrivalICAO,)
+        print >> f, "planecode=%s" % \
+              (BookedFlight.TYPE2TYPECODE[self.aircraftType],)
+        print >> f, "planetype=%s" % (self.aircraftTypeName,)
+        print >> f, "tail_nr=%s" % (self.tailNumber,)
+        print >> f, "passenger=%d" % (self.numPassengers,)
+        print >> f, "crew=%d" % (self.numCrew,)
+        print >> f, "bag=%d" % (self.bagWeight,)
+        print >> f, "cargo=%d" % (self.cargoWeight,)
+        print >> f, "mail=%d" % (self.mailWeight,)
+        print >> f, "flight_route=%s" % (self.route,)
+        departureTime = self.departureTime
+        print >> f, "departure_time=%02d\\:%02d\\:%02d" % \
+              (departureTime.hour, departureTime.minute, departureTime.second)
+        arrivalTime = self.arrivalTime
+        print >> f, "arrival_time=%02d\\:%02d\\:%02d" % \
+              (arrivalTime.hour, arrivalTime.minute, arrivalTime.second)
+        print >> f, "foglalas_id=%s" % ("0" if self.id is None else self.id,)
+
+    def _readAircraftType(self, f):
+        """Read the aircraft type from the given file."""
+        line = readline(f)
+        typeCode = line[:3]
+        self.aircraftType = self._decodeAircraftType(typeCode)
+        self.aircraftTypeName = line[3:]
+
+    def _decodeAircraftType(self, typeCode):
+        """Decode the aircraft type from the given typeCode."""
+        if typeCode in self.TYPECODE2TYPE:
+            return 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, ident, basic,
+                 begin, notice, end = None, permanent = False,
+                 repeatCycle = None):
+        """Construct the NOTAM."""
+        self.ident = ident
+        self.basic = basic
+        self.begin = begin
+        self.notice = notice
+        self.end = end
+        self.permanent = permanent
+        self.repeatCycle = repeatCycle
+
+    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
+
+    def __str__(self):
+        """Get the string representation of the NOTAM."""
+        s = ""
+        s += str(self.ident) + " " + str(self.basic) + "\n"
+        s += str(self.begin)
+        if self.end is not None:
+            s += " - " + str(self.end)
+        elif self.permanent:
+            s += " - PERMANENT"
+        s += "\n"
+        if self.repeatCycle:
+            s += "Repeat cycle: " + self.repeatCycle + "\n"
+        s += self.notice + "\n"
+        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 \
+           "ident" not in attrs or not attrs["ident"] or \
+           "Q" not in attrs or not attrs["Q"] 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(attrs["ident"], attrs["Q"],
+                                        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 PilotsWebNOTAMsParser(HTMLParser.HTMLParser):
+    """XML handler for the NOTAM query results on the PilotsWeb website."""
+    def __init__(self):
+        """Construct the handler."""
+        HTMLParser.HTMLParser.__init__(self)
+
+        self._notams = []
+        self._currentNOTAM = ""
+        self._stage = 0
+
+    def handle_starttag(self, name, attrs):
+        """Start an element."""
+        if (self._stage==0 and name=="div" and ("id", "notamRight") in attrs) or \
+           (self._stage==1 and name=="span") or \
+           (self._stage==2 and name=="pre"):
+            self._stage += 1
+            if self._stage==1:
+                self._currentNOTAM = ""
+
+    def handle_data(self, content):
+        """Handle characters"""
+        if self._stage==3:
+            self._currentNOTAM += content
+
+    def handle_endtag(self, name):
+        """End an element."""
+        if (self._stage==3 and name=="pre") or \
+           (self._stage==2 and name=="span") or \
+           (self._stage==1 and name=="div"):
+            self._stage -= 1
+            if self._stage==0:
+                self._processCurrentNOTAM()
+
+    def getNOTAMs(self):
+        """Get the NOTAMs collected"""
+        return self._notams
+
+    def _processCurrentNOTAM(self):
+        """Parse the current NOTAM and append its contents to the list of
+        NOTAMS."""
+        notam = None
+        try:
+            notam = self._parseCurrentNOTAM2()
+        except Exception, e:
+            print "Error parsing current NOTAM: " + str(e)
+
+        if notam is None:
+            print "Could not parse NOTAM: " + self._currentNOTAM
+            if self._currentNOTAM:
+                self._notams.append(self._currentNOTAM + "\n")
+        else:
+            self._notams.append(notam)
+
+    def _parseCurrentNOTAM(self):
+        """Parse the current NOTAM, if possible, and return a NOTAM object."""
+        lines = self._currentNOTAM.splitlines()
+        lines = map(lambda line: line.strip(), lines)
+
+        if len(lines)<4:
+            return None
+
+        if not lines[1].startswith("Q)") or \
+           not lines[2].startswith("A)") or \
+           not (lines[3].startswith("E)") or
+                (lines[3].startswith("D)") and lines[4].startswith("E)"))):
+            return None
+
+        ident = lines[0].split()[0]
+        basic = lines[1][2:].strip()
+
+        words = lines[2].split()
+        if len(words)<4 or words[0]!="A)" or words[2]!="B)":
+            return None
+
+        begin = datetime.datetime.strptime(words[3], "%y%m%d%H%M")
+        end = None
+        permanent = False
+        if words[4]=="C)" and len(words)>=6:
+            if words[5] in ["PERM", "UFN"]:
+                permanent = True
+            else:
+                end = datetime.datetime.strptime(words[5], "%y%m%d%H%M")
+        else:
+            permanent = True
+
+        repeatCycle = None
+        noticeStartIndex = 3
+        if lines[3].startswith("D)"):
+            repeatCycle = lines[3][2:].strip()
+            noticeStartIndex = 4
+
+        notice = ""
+        for index in range(noticeStartIndex, len(lines)):
+            line = lines[index][2:] if index==noticeStartIndex else lines[index]
+            line = line.strip()
+
+            if line.lower().startswith("created:") or \
+               line.lower().startswith("source:"):
+               break
+
+            if notice: notice += " "
+            notice += line
+
+        return NOTAM(ident, basic, begin, notice, end = end,
+                     permanent = permanent, repeatCycle = repeatCycle)
+
+    def _parseCurrentNOTAM2(self):
+        """Parse the current NOTAM with a second, more flexible method."""
+        lines = self._currentNOTAM.splitlines()
+        lines = map(lambda line: line.strip(), lines)
+
+        if not lines:
+            return None
+
+        ident = lines[0].split()[0]
+
+        lines = lines[1:]
+        for i in range(0, 2):
+            l = lines[-1].lower()
+            if l.startswith("created:") or l.startswith("source:"):
+                lines = lines[:-1]
+
+        lines = map(lambda line: line.strip(), lines)
+        contents = " ".join(lines).split()
+
+        items = {}
+        for i in ["Q)", "A)", "B)", "C)", "D)", "E)"]:
+            items[i] = ""
+
+        currentItem = None
+        for word in contents:
+            if word in items:
+                currentItem = word
+            elif currentItem in items:
+                s = items[currentItem]
+                if s: s+= " "
+                s += word
+                items[currentItem] = s
+
+        if not items["Q)"] or not items["A)"] or not items["B)"] or \
+           not items["E)"]:
+            return None
+
+        basic = items["Q)"]
+        begin = datetime.datetime.strptime(items["B)"], "%y%m%d%H%M")
+
+        end = None
+        permanent = False
+        if items["C)"]:
+            endItem = items["C)"]
+            if endItem in ["PERM", "UFN"]:
+                permanent = True
+            else:
+                end = datetime.datetime.strptime(items["C)"], "%y%m%d%H%M")
+        else:
+            permanent = True
+
+        repeatCycle = None
+        if items["D)"]:
+            repeatCycle = items["D)"]
+
+        notice = items["E)"]
+
+        return NOTAM(ident, basic, begin, notice, end = end,
+                     permanent = permanent, repeatCycle = repeatCycle)
+
+#------------------------------------------------------------------------------
+
+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: " + util.utf2unicode(str(e))
+            #traceback.print_exc()
+
+#------------------------------------------------------------------------------
+
+class Login(Request):
+    """A login request."""
+    iso88592decoder = codecs.getdecoder("iso-8859-2")
+
+    def __init__(self, callback, pilotID, password, entranceExam):
+        """Construct the login request with the given pilot ID and
+        password."""
+        super(Login, self).__init__(callback)
+
+        self._pilotID = pilotID
+        self._password = password
+        self._entranceExam = entranceExam
+
+    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()
+
+        if self._entranceExam:
+            url = "http://www.virtualairlines.hu/ellenorzo/getflightplan.php?pid=%s" % \
+                  (pilotID,)
+        else:
+            url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
+                  (pilotID, password)
+
+        result = Result()
+        result.entranceExam = self._entranceExam
+
+        f = urllib2.urlopen(url, timeout = 10.0)
+
+        status = readline(f)
+        if self._entranceExam:
+            result.loggedIn = status != "#NOEXAM"
+        else:
+            result.loggedIn = status == ".OK."
+
+        if result.loggedIn:
+            result.pilotID = self._pilotID
+            result.password = self._password
+            result.flights = []
+            # FIXME: this may not be the correct behaviour
+            # for an entrance exam, but the website returns
+            # an error
+            if self._entranceExam:
+                result.pilotName = result.pilotID
+                result.exams = ""
+            else:
+                result.pilotName = self.iso88592decoder(readline(f))[0]
+                result.exams = readline(f)
+
+                while True:
+                    line = readline(f)
+                    if not line or line == "#ENDPIREP": break
+
+                    flight = BookedFlight(line)
+                    flight.readFromWeb(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."""
+        departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
+        arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
+
+        icaos = []
+        if not departureNOTAMs: icaos.append(self._departureICAO)
+        if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
+
+        if icaos:
+            xmlParser = xml.sax.make_parser()
+            notamHandler = NOTAMHandler(icaos)
+            xmlParser.setContentHandler(notamHandler)
+
+            url = "http://notams.euroutepro.com/notams.xml"
+
+            f = urllib2.urlopen(url, timeout = 10.0)
+            try:
+                xmlParser.parse(f)
+            finally:
+                f.close()
+
+            for icao in icaos:
+                if icao==self._departureICAO:
+                    departureNOTAMs = notamHandler.get(icao)
+                else:
+                    arrivalNOTAMs = notamHandler.get(icao)
+
+        result = Result()
+        result.departureNOTAMs = departureNOTAMs
+        result.arrivalNOTAMs = arrivalNOTAMs
+
+        return result
+
+    def getPilotsWebNOTAMs(self, icao):
+        """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
+        code.
+
+        Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
+        try:
+            parser = PilotsWebNOTAMsParser()
+
+            url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
+              (icao.upper(),)
+
+            f = urllib2.urlopen(url, timeout = 10.0)
+            try:
+                data = f.read(16384)
+                while data:
+                    parser.feed(data)
+                    data = f.read(16384)
+            finally:
+                f.close()
+
+            return parser.getNOTAMs()
+
+        except Exception, e:
+            traceback.print_exc()
+            print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
+                  (icao, str(e))
+            return None
+
+#------------------------------------------------------------------------------
+
+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 sending of the 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"
+        #url = "http://localhost:15000"
+
+        pirep = self._pirep
+
+        data = {}
+        data["acarsdata"] = SendPIREP._latin2Encoder(pirep.getACARSText())[0]
+
+        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(pirep.numPassengers)
+        data["crew"] = str(pirep.numCrew)
+        data["cargo"] = str(pirep.cargoWeight)
+        data["bag"] = str(pirep.bagWeight)
+        data["mail"] = str(pirep.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"] = "%.2f" % (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")
+
+        postData = urllib.urlencode(data)
+        f = urllib2.urlopen(url, postData, 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."""
+        print "Sending the online 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.aircraftTypeName
+        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 SendBugReport(Request):
+    """A request to send a bug report to the project homepage."""
+    _latin2Encoder = codecs.getencoder("iso-8859-2")
+
+    def __init__(self, callback, summary, description, email):
+        """Construct the request for the given bug report."""
+        super(SendBugReport, self).__init__(callback)
+        self._summary = summary
+        self._description = description
+        self._email = email
+
+    def run(self):
+        """Perform the sending of the bug report."""
+        serverProxy = xmlrpclib.ServerProxy("http://mlx.varadiistvan.hu/rpc")
+
+        result = Result()
+        result.success = False
+
+        attributes = {}
+        if self._email:
+            attributes["reporter"] = self._email
+
+        result.ticketID = serverProxy.ticket.create(self._summary, self._description,
+                                                    attributes, True)
+        print "Created ticket with ID:", result.ticketID
+        result.success = True
+
+        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, entranceExam = False):
+        """Enqueue a login request."""
+        self._addRequest(Login(callback, pilotID, password, entranceExam))
+
+    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 sendBugReport(self, callback, summary, description, email):
+        """Send a bug report with the given data."""
+        self._addRequest(SendBugReport(callback, summary, description, email))
+
+    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)
+
+    handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
+    time.sleep(3)
+
+#------------------------------------------------------------------------------
Index: src/mlx/web.py
===================================================================
--- src/mlx/web.py	(revision 570)
+++ 	(revision )
@@ -1,1149 +1,0 @@
-
-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
-import xmlrpclib
-import HTMLParser
-
-#---------------------------------------------------------------------------------------
-
-## @package mlx.web
-#
-# Web interface.
-#
-# This module implements a thread that can perform (HTTP) requests
-# asynchronously. When the request is performed, a callback is called. The main
-# interface is the \ref Handler class. Each of its functions creates a \ref
-# Request subclass instance and puts it to the request queue. The handler
-# thread then takes the requests one by one, and executes them.
-#
-# This module also defines some data classes the contents of which are
-# retrieved or sent via HTTP. \ref BookedFlight contains data of a flight
-# booked on the MAVA website, \ref Fleet and \ref Plane represents the MAVA
-# fleet and the gates at Ferihegy and \ref NOTAM is a NOTAM.
-
-#---------------------------------------------------------------------------------------
-
-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,
-                      "73H"  : const.AIRCRAFT_B738C,
-                      "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,
-                      "146"  : const.AIRCRAFT_B462 }
-
-    TYPE2TYPECODE = { const.AIRCRAFT_B736  : "736",
-                      const.AIRCRAFT_B737  : "73G",
-                      const.AIRCRAFT_B738  : "738",
-                      const.AIRCRAFT_B738C : "73H",
-                      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",
-                      const.AIRCRAFT_B462  : "146" }
-
-    @staticmethod
-    def getDateTime(date, time):
-        """Get a datetime object from the given textual date and time."""
-        return datetime.datetime.strptime(date + " " + time,
-                                          "%Y-%m-%d %H:%M:%S")
-
-    def __init__(self, id = None):
-        """Construct a booked flight with the given ID."""
-        self.id = id
-
-    def readFromWeb(self, f):
-        """Read the data of the flight from the web via the given file
-        object."""
-        self.callsign = readline(f)
-
-        date = readline(f)
-        print "web.BookedFlight.readFromWeb: date:", date
-        if date=="0000-00-00": date = "0001-01-01"
-
-        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 = BookedFlight.getDateTime(date, departureTime)
-
-        arrivalTime = readline(f)
-        self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
-        if self.arrivalTime<self.departureTime:
-            self.arrivalTime += datetime.timedelta(days = 1)
-
-        if not readline(f)==".NEXT.":
-            raise Exception("Invalid line in flight data")
-
-    def readFromFile(self, f):
-        """Read the data of the flight from a file via the given file
-        object."""
-        date = None
-        departureTime = None
-        arrivalTime = None
-
-        line = f.readline()
-        lineNumber = 0
-        while line:
-            lineNumber += 1
-            line = line.strip()
-
-            hashIndex = line.find("#")
-            if hashIndex>=0: line = line[:hashIndex]
-            if line:
-                equalIndex = line.find("=")
-                lineOK = equalIndex>0
-
-                if lineOK:
-                    key = line[:equalIndex].strip()
-                    value = line[equalIndex+1:].strip().replace("\:", ":")
-
-                    lineOK = key and value
-
-                if lineOK:
-                    if key=="callsign": self.callsign = value
-                    elif key=="date": date = value
-                    elif key=="dep_airport": self.departureICAO = value
-                    elif key=="dest_airport": self.arrivalICAO = value
-                    elif key=="planecode": self.aircraftType = \
-                         self._decodeAircraftType(value)
-                    elif key=="planetype": self.aircraftTypeName = value
-                    elif key=="tail_nr": self.tailNumber = value
-                    elif key=="passenger": self.numPassengers = int(value)
-                    elif key=="crew": self.numCrew = int(value)
-                    elif key=="bag": self.bagWeight = int(value)
-                    elif key=="cargo": self.cargoWeight = int(value)
-                    elif key=="mail": self.mailWeight = int(value)
-                    elif key=="flight_route": self.route = value
-                    elif key=="departure_time": departureTime = value
-                    elif key=="arrival_time": arrivalTime = value
-                    elif key=="foglalas_id":
-                        self.id = None if value=="0" else value
-                    else: lineOK = False
-
-                if not lineOK:
-                    print "web.BookedFlight.readFromFile: line %d is invalid" % \
-                          (lineNumber,)
-
-            line = f.readline()
-
-        if date is not None:
-            if departureTime is not None:
-                self.departureTime = BookedFlight.getDateTime(date,
-                                                              departureTime)
-            if arrivalTime is not None:
-                self.arrivalTime = BookedFlight.getDateTime(date,
-                                                            arrivalTime)
-
-        d = dir(self)
-        for attribute in ["callsign", "departureICAO", "arrivalICAO",
-                          "aircraftType", "tailNumber",
-                          "numPassengers", "numCrew",
-                          "bagWeight", "cargoWeight", "mailWeight",
-                          "route", "departureTime", "arrivalTime"]:
-            if attribute not in d:
-                raise Exception("Attribute %s could not be read" % (attribute,))
-
-        if "aircraftTypeName" not in d:
-            self.aircraftTypeName = \
-                BookedFlight.TYPE2TYPECODE[self.aircraftType]
-
-    def writeIntoFile(self, f):
-        """Write the flight into a file."""
-        print >> f, "callsign=%s" % (self.callsign,)
-        date = self.departureTime.date()
-        print >> f, "date=%04d-%02d-%0d" % (date.year, date.month, date.day)
-        print >> f, "dep_airport=%s" % (self.departureICAO,)
-        print >> f, "dest_airport=%s" % (self.arrivalICAO,)
-        print >> f, "planecode=%s" % \
-              (BookedFlight.TYPE2TYPECODE[self.aircraftType],)
-        print >> f, "planetype=%s" % (self.aircraftTypeName,)
-        print >> f, "tail_nr=%s" % (self.tailNumber,)
-        print >> f, "passenger=%d" % (self.numPassengers,)
-        print >> f, "crew=%d" % (self.numCrew,)
-        print >> f, "bag=%d" % (self.bagWeight,)
-        print >> f, "cargo=%d" % (self.cargoWeight,)
-        print >> f, "mail=%d" % (self.mailWeight,)
-        print >> f, "flight_route=%s" % (self.route,)
-        departureTime = self.departureTime
-        print >> f, "departure_time=%02d\\:%02d\\:%02d" % \
-              (departureTime.hour, departureTime.minute, departureTime.second)
-        arrivalTime = self.arrivalTime
-        print >> f, "arrival_time=%02d\\:%02d\\:%02d" % \
-              (arrivalTime.hour, arrivalTime.minute, arrivalTime.second)
-        print >> f, "foglalas_id=%s" % ("0" if self.id is None else self.id,)
-
-    def _readAircraftType(self, f):
-        """Read the aircraft type from the given file."""
-        line = readline(f)
-        typeCode = line[:3]
-        self.aircraftType = self._decodeAircraftType(typeCode)
-        self.aircraftTypeName = line[3:]
-
-    def _decodeAircraftType(self, typeCode):
-        """Decode the aircraft type from the given typeCode."""
-        if typeCode in self.TYPECODE2TYPE:
-            return 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, ident, basic,
-                 begin, notice, end = None, permanent = False,
-                 repeatCycle = None):
-        """Construct the NOTAM."""
-        self.ident = ident
-        self.basic = basic
-        self.begin = begin
-        self.notice = notice
-        self.end = end
-        self.permanent = permanent
-        self.repeatCycle = repeatCycle
-
-    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
-
-    def __str__(self):
-        """Get the string representation of the NOTAM."""
-        s = ""
-        s += str(self.ident) + " " + str(self.basic) + "\n"
-        s += str(self.begin)
-        if self.end is not None:
-            s += " - " + str(self.end)
-        elif self.permanent:
-            s += " - PERMANENT"
-        s += "\n"
-        if self.repeatCycle:
-            s += "Repeat cycle: " + self.repeatCycle + "\n"
-        s += self.notice + "\n"
-        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 \
-           "ident" not in attrs or not attrs["ident"] or \
-           "Q" not in attrs or not attrs["Q"] 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(attrs["ident"], attrs["Q"],
-                                        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 PilotsWebNOTAMsParser(HTMLParser.HTMLParser):
-    """XML handler for the NOTAM query results on the PilotsWeb website."""
-    def __init__(self):
-        """Construct the handler."""
-        HTMLParser.HTMLParser.__init__(self)
-
-        self._notams = []
-        self._currentNOTAM = ""
-        self._stage = 0
-
-    def handle_starttag(self, name, attrs):
-        """Start an element."""
-        if (self._stage==0 and name=="div" and ("id", "notamRight") in attrs) or \
-           (self._stage==1 and name=="span") or \
-           (self._stage==2 and name=="pre"):
-            self._stage += 1
-            if self._stage==1:
-                self._currentNOTAM = ""
-
-    def handle_data(self, content):
-        """Handle characters"""
-        if self._stage==3:
-            self._currentNOTAM += content
-
-    def handle_endtag(self, name):
-        """End an element."""
-        if (self._stage==3 and name=="pre") or \
-           (self._stage==2 and name=="span") or \
-           (self._stage==1 and name=="div"):
-            self._stage -= 1
-            if self._stage==0:
-                self._processCurrentNOTAM()
-
-    def getNOTAMs(self):
-        """Get the NOTAMs collected"""
-        return self._notams
-
-    def _processCurrentNOTAM(self):
-        """Parse the current NOTAM and append its contents to the list of
-        NOTAMS."""
-        notam = None
-        try:
-            notam = self._parseCurrentNOTAM2()
-        except Exception, e:
-            print "Error parsing current NOTAM: " + str(e)
-
-        if notam is None:
-            print "Could not parse NOTAM: " + self._currentNOTAM
-            if self._currentNOTAM:
-                self._notams.append(self._currentNOTAM + "\n")
-        else:
-            self._notams.append(notam)
-
-    def _parseCurrentNOTAM(self):
-        """Parse the current NOTAM, if possible, and return a NOTAM object."""
-        lines = self._currentNOTAM.splitlines()
-        lines = map(lambda line: line.strip(), lines)
-
-        if len(lines)<4:
-            return None
-
-        if not lines[1].startswith("Q)") or \
-           not lines[2].startswith("A)") or \
-           not (lines[3].startswith("E)") or
-                (lines[3].startswith("D)") and lines[4].startswith("E)"))):
-            return None
-
-        ident = lines[0].split()[0]
-        basic = lines[1][2:].strip()
-
-        words = lines[2].split()
-        if len(words)<4 or words[0]!="A)" or words[2]!="B)":
-            return None
-
-        begin = datetime.datetime.strptime(words[3], "%y%m%d%H%M")
-        end = None
-        permanent = False
-        if words[4]=="C)" and len(words)>=6:
-            if words[5] in ["PERM", "UFN"]:
-                permanent = True
-            else:
-                end = datetime.datetime.strptime(words[5], "%y%m%d%H%M")
-        else:
-            permanent = True
-
-        repeatCycle = None
-        noticeStartIndex = 3
-        if lines[3].startswith("D)"):
-            repeatCycle = lines[3][2:].strip()
-            noticeStartIndex = 4
-
-        notice = ""
-        for index in range(noticeStartIndex, len(lines)):
-            line = lines[index][2:] if index==noticeStartIndex else lines[index]
-            line = line.strip()
-
-            if line.lower().startswith("created:") or \
-               line.lower().startswith("source:"):
-               break
-
-            if notice: notice += " "
-            notice += line
-
-        return NOTAM(ident, basic, begin, notice, end = end,
-                     permanent = permanent, repeatCycle = repeatCycle)
-
-    def _parseCurrentNOTAM2(self):
-        """Parse the current NOTAM with a second, more flexible method."""
-        lines = self._currentNOTAM.splitlines()
-        lines = map(lambda line: line.strip(), lines)
-
-        if not lines:
-            return None
-
-        ident = lines[0].split()[0]
-
-        lines = lines[1:]
-        for i in range(0, 2):
-            l = lines[-1].lower()
-            if l.startswith("created:") or l.startswith("source:"):
-                lines = lines[:-1]
-
-        lines = map(lambda line: line.strip(), lines)
-        contents = " ".join(lines).split()
-
-        items = {}
-        for i in ["Q)", "A)", "B)", "C)", "D)", "E)"]:
-            items[i] = ""
-
-        currentItem = None
-        for word in contents:
-            if word in items:
-                currentItem = word
-            elif currentItem in items:
-                s = items[currentItem]
-                if s: s+= " "
-                s += word
-                items[currentItem] = s
-
-        if not items["Q)"] or not items["A)"] or not items["B)"] or \
-           not items["E)"]:
-            return None
-
-        basic = items["Q)"]
-        begin = datetime.datetime.strptime(items["B)"], "%y%m%d%H%M")
-
-        end = None
-        permanent = False
-        if items["C)"]:
-            endItem = items["C)"]
-            if endItem in ["PERM", "UFN"]:
-                permanent = True
-            else:
-                end = datetime.datetime.strptime(items["C)"], "%y%m%d%H%M")
-        else:
-            permanent = True
-
-        repeatCycle = None
-        if items["D)"]:
-            repeatCycle = items["D)"]
-
-        notice = items["E)"]
-
-        return NOTAM(ident, basic, begin, notice, end = end,
-                     permanent = permanent, repeatCycle = repeatCycle)
-
-#------------------------------------------------------------------------------
-
-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: " + util.utf2unicode(str(e))
-            #traceback.print_exc()
-
-#------------------------------------------------------------------------------
-
-class Login(Request):
-    """A login request."""
-    iso88592decoder = codecs.getdecoder("iso-8859-2")
-
-    def __init__(self, callback, pilotID, password, entranceExam):
-        """Construct the login request with the given pilot ID and
-        password."""
-        super(Login, self).__init__(callback)
-
-        self._pilotID = pilotID
-        self._password = password
-        self._entranceExam = entranceExam
-
-    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()
-
-        if self._entranceExam:
-            url = "http://www.virtualairlines.hu/ellenorzo/getflightplan.php?pid=%s" % \
-                  (pilotID,)
-        else:
-            url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
-                  (pilotID, password)
-
-        result = Result()
-        result.entranceExam = self._entranceExam
-
-        f = urllib2.urlopen(url, timeout = 10.0)
-
-        status = readline(f)
-        if self._entranceExam:
-            result.loggedIn = status != "#NOEXAM"
-        else:
-            result.loggedIn = status == ".OK."
-
-        if result.loggedIn:
-            result.pilotID = self._pilotID
-            result.password = self._password
-            result.flights = []
-            # FIXME: this may not be the correct behaviour
-            # for an entrance exam, but the website returns
-            # an error
-            if self._entranceExam:
-                result.pilotName = result.pilotID
-                result.exams = ""
-            else:
-                result.pilotName = self.iso88592decoder(readline(f))[0]
-                result.exams = readline(f)
-
-                while True:
-                    line = readline(f)
-                    if not line or line == "#ENDPIREP": break
-
-                    flight = BookedFlight(line)
-                    flight.readFromWeb(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."""
-        departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
-        arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
-
-        icaos = []
-        if not departureNOTAMs: icaos.append(self._departureICAO)
-        if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
-
-        if icaos:
-            xmlParser = xml.sax.make_parser()
-            notamHandler = NOTAMHandler(icaos)
-            xmlParser.setContentHandler(notamHandler)
-
-            url = "http://notams.euroutepro.com/notams.xml"
-
-            f = urllib2.urlopen(url, timeout = 10.0)
-            try:
-                xmlParser.parse(f)
-            finally:
-                f.close()
-
-            for icao in icaos:
-                if icao==self._departureICAO:
-                    departureNOTAMs = notamHandler.get(icao)
-                else:
-                    arrivalNOTAMs = notamHandler.get(icao)
-
-        result = Result()
-        result.departureNOTAMs = departureNOTAMs
-        result.arrivalNOTAMs = arrivalNOTAMs
-
-        return result
-
-    def getPilotsWebNOTAMs(self, icao):
-        """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
-        code.
-
-        Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
-        try:
-            parser = PilotsWebNOTAMsParser()
-
-            url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
-              (icao.upper(),)
-
-            f = urllib2.urlopen(url, timeout = 10.0)
-            try:
-                data = f.read(16384)
-                while data:
-                    parser.feed(data)
-                    data = f.read(16384)
-            finally:
-                f.close()
-
-            return parser.getNOTAMs()
-
-        except Exception, e:
-            traceback.print_exc()
-            print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
-                  (icao, str(e))
-            return None
-
-#------------------------------------------------------------------------------
-
-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 sending of the 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"
-        #url = "http://localhost:15000"
-
-        pirep = self._pirep
-
-        data = {}
-        data["acarsdata"] = SendPIREP._latin2Encoder(pirep.getACARSText())[0]
-
-        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(pirep.numPassengers)
-        data["crew"] = str(pirep.numCrew)
-        data["cargo"] = str(pirep.cargoWeight)
-        data["bag"] = str(pirep.bagWeight)
-        data["mail"] = str(pirep.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"] = "%.2f" % (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")
-
-        postData = urllib.urlencode(data)
-        f = urllib2.urlopen(url, postData, 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."""
-        print "Sending the online 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.aircraftTypeName
-        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 SendBugReport(Request):
-    """A request to send a bug report to the project homepage."""
-    _latin2Encoder = codecs.getencoder("iso-8859-2")
-
-    def __init__(self, callback, summary, description, email):
-        """Construct the request for the given bug report."""
-        super(SendBugReport, self).__init__(callback)
-        self._summary = summary
-        self._description = description
-        self._email = email
-
-    def run(self):
-        """Perform the sending of the bug report."""
-        serverProxy = xmlrpclib.ServerProxy("http://mlx.varadiistvan.hu/rpc")
-
-        result = Result()
-        result.success = False
-
-        attributes = {}
-        if self._email:
-            attributes["reporter"] = self._email
-
-        result.ticketID = serverProxy.ticket.create(self._summary, self._description,
-                                                    attributes, True)
-        print "Created ticket with ID:", result.ticketID
-        result.success = True
-
-        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, entranceExam = False):
-        """Enqueue a login request."""
-        self._addRequest(Login(callback, pilotID, password, entranceExam))
-
-    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 sendBugReport(self, callback, summary, description, email):
-        """Send a bug report with the given data."""
-        self._addRequest(SendBugReport(callback, summary, description, email))
-
-    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)
-
-    handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
-    time.sleep(3)
-
-#------------------------------------------------------------------------------
Index: src/mlx/xplane.py
===================================================================
--- src/mlx/xplane.py	(revision 642)
+++ src/mlx/xplane.py	(revision 642)
@@ -0,0 +1,2014 @@
+
+import fs
+import const
+import util
+from watchdog import Watchdog
+
+import threading
+import time
+import calendar
+import datetime
+import sys
+import codecs
+import math
+
+from xplra import XPlane, MultiGetter, MultiSetter, ProtocolException
+from xplra import TYPE_INT, TYPE_FLOAT, TYPE_DOUBLE
+from xplra import TYPE_FLOAT_ARRAY, TYPE_INT_ARRAY, TYPE_BYTE_ARRAY
+from xplra import HOTKEY_MODIFIER_SHIFT, HOTKEY_MODIFIER_CONTROL
+
+#------------------------------------------------------------------------------
+
+## @package mlx.xplane
+#
+# The module towards X-Plane
+#
+# This module implements the simulator interface to X-Plane via the
+# X-Plane Remote Access (xplra) plugin.
+
+#------------------------------------------------------------------------------
+
+_hgin2hpa = 1013.25 / 29.92
+
+_mps2knots = 3600.0 / 1852
+
+#------------------------------------------------------------------------------
+
+class Request(object):
+    """Base class for one-shot requests."""
+    def __init__(self, handler, callback, extra):
+        """Construct the request."""
+        self._handler = handler
+        self._callback = callback
+        self._extra = extra
+        self._result = None
+
+    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._process(time):
+            Handler._callSafe(lambda: self._callback(self._result,
+                                                     self._extra))
+            return True
+        else:
+            return False
+
+    def fail(self):
+        """Handle the failure of this request."""
+        Handler._callSafe(lambda: self._callback(False, self._extra))
+
+class DataRequest(Request):
+    """A simple, one-shot data read or write request."""
+    def __init__(self, handler, forWrite, data, callback, extra,
+                 validator = None):
+        """Construct the request."""
+        super(DataRequest, self).__init__(handler, callback, extra)
+
+        self._forWrite = forWrite
+        self._validator = validator
+
+        xplane = handler._xplane
+        self._multiBuffer = xplane.createMultiSetter() if forWrite \
+                            else xplane.createMultiGetter()
+
+        Handler._setupMultiBuffer(self._multiBuffer,
+                                  [(d[0], d[1]) for d in data])
+
+        if forWrite:
+            index = 0
+            for (_, _, value) in data:
+                self._multiBuffer[index] = value
+                index += 1
+
+
+    def fail(self):
+        """Handle the failure of this request."""
+        if self._forWrite:
+            super(DataRequest, self).fail()
+        else:
+            Handler._callSafe(lambda: self._callback(None, self._extra))
+
+    def _process(self, time):
+        """Process the request."""
+        if self._forWrite:
+            self._multiBuffer.execute()
+            self._result = True
+            return True
+
+        try:
+            if Handler._performRead(self._multiBuffer,
+                                    self._extra, self._validator):
+                self._result = self._multiBuffer
+                return True
+            else:
+                return False
+        except ProtocolException, e:
+            self._result = None
+            return True
+
+class ShowMessageRequest(Request):
+    """Request to show a message in the simulator window."""
+    def __init__(self, handler, message, duration, callback, extra):
+        """Construct the request."""
+        super(ShowMessageRequest, self).__init__(handler,
+                                                         callback, extra)
+        self._message = message
+        self._duration = duration
+
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.showMessage(self._message, self._duration)
+        self._result = True
+        return True
+
+class RegisterHotkeysRequest(Request):
+    """Request to register hotkeys with the simulator."""
+    def __init__(self, handler, hotkeyCodes, callback, extra):
+        """Construct the request."""
+        super(RegisterHotkeysRequest, self).__init__(handler,
+                                                     callback,
+                                                     extra)
+        self._hotkeyCodes = hotkeyCodes
+
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.registerHotkeys(self._hotkeyCodes)
+        self._result = True
+        return True
+
+class UnregisterHotkeysRequest(Request):
+    """Request to register hotkeys with the simulator."""
+    def _process(self, time):
+        """Process the request."""
+        self._handler._xplane.unregisterHotkeys()
+        self._result = True
+        return True
+
+class PeriodicRequest(object):
+    """A periodic request."""
+    def __init__(self, handler, id, period, callback, extra):
+        """Construct the periodic request."""
+        self._handler = handler
+        self._id = id
+        self._period = period
+        self._nextFire = time.time()
+        self._callback = callback
+        self._extra = extra
+        self._result = None
+
+    @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, now):
+        """Check if this request should be executed, and if so, do so.
+
+        now 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 now<self._nextFire:
+            return True
+
+        isOK = self._process(time)
+
+        if isOK:
+            Handler._callSafe(lambda: self._callback(self._result,
+                                                     self._extra))
+            now = time.time()
+            while self._nextFire <= now:
+                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)
+
+class PeriodicDataRequest(PeriodicRequest):
+    """A periodic request."""
+    def __init__(self, handler, id, period, data, callback, extra,
+                 validator):
+        """Construct the periodic request."""
+        super(PeriodicDataRequest, self).__init__(handler, id, period,
+                                                  callback, extra)
+        self._validator = validator
+        self._multiGetter = handler._xplane.createMultiGetter()
+        Handler._setupMultiBuffer(self._multiGetter, data)
+
+    def _process(self, now):
+        """Process the request."""
+        if Handler._performRead(self._multiGetter,
+                                self._extra, self._validator):
+            self._result = self._multiGetter
+            return True
+        else:
+            return False
+
+#------------------------------------------------------------------------------
+
+class HotkeysStateRequest(PeriodicRequest):
+    """Periodic hotkey query request."""
+    def _process(self, now):
+        """Process the request."""
+        self._result = self._handler._xplane.queryHotkeys()
+        return True
+
+#------------------------------------------------------------------------------
+
+class Handler(threading.Thread):
+    """The thread to handle the requests towards X-Plane."""
+    @staticmethod
+    def _callSafe(fun):
+        """Call the given function and swallow any exceptions."""
+        try:
+            return fun()
+        except Exception, e:
+            print >> sys.stderr, util.utf2unicode(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 _setupMultiBuffer(buffer, dataSpec):
+        """Setup the given multi-dataref buffer for the given data
+        specification.
+
+        The specification is a list of tuples of two items:
+        - the name of the dataref
+        - the type of the dataref. It can be one of the following:
+          - an integer denoting the type. If it denotes an array type, the
+            length will be -1 (i.e. as many as returned when reading the
+            value), and the offset will be 0
+          - a tuple of two or three items:
+            - the first item is the type constant
+            - the second item is the length
+            - the third item is the offset, which defaults to 0."""
+        for (name, typeInfo) in dataSpec:
+            length = -1
+            offset = 0
+            type = 0
+            if isinstance(typeInfo, tuple):
+                type = typeInfo[0]
+                length = typeInfo[1]
+                offset = 0 if len(typeInfo)<3 else typeInfo[2]
+            else:
+                type = typeInfo
+
+            if type==TYPE_INT:
+                buffer.addInt(name)
+            elif type==TYPE_FLOAT:
+                buffer.addFloat(name)
+            elif type==TYPE_DOUBLE:
+                buffer.addDouble(name)
+            elif type==TYPE_FLOAT_ARRAY:
+                buffer.addFloatArray(name, length = length, offset = offset)
+            elif type==TYPE_INT_ARRAY:
+                buffer.addIntArray(name, length = length, offset = offset)
+            elif type==TYPE_BYTE_ARRAY:
+                buffer.addByteArray(name, length = length, offset = offset)
+            else:
+                raise TypeError("xplane.Handler._setupMultiBuffer: invalid type info: %s for dataref '%s'" % (typeInfo, name))
+
+
+
+    @staticmethod
+    def _performRead(multiGetter, 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:
+            try:
+                multiGetter.execute()
+            except ProtocolException, e:
+                print "xplane.Handler._performRead: " + str(e)
+                raise
+
+            if validator is None or \
+               Handler._callSafe(lambda: validator(multiGetter, extra)):
+                return True
+            else:
+                attemptsLeft -= 1
+        return False
+
+    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._xplane = XPlane()
+
+        self._requestCondition = threading.Condition()
+        self._connectionRequested = False
+        self._connected = False
+
+        self._requests = []
+        self._nextPeriodicID = 1
+        self._periodicRequests = []
+
+        self._watchdogClient = Watchdog.get().addClient(2.0, "xplane.Handler")
+
+        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(DataRequest(self, 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 = DataRequest(self, True, data, callback, extra)
+            #print "xplane.Handler.requestWrite", request
+            self._requests.append(request)
+            self._requestCondition.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 = PeriodicDataRequest(self, 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 requestShowMessage(self, message, duration, callback, extra = None):
+        """Request showing a message in the simulator."""
+        with self._requestCondition:
+            self._requests.append(ShowMessageRequest(self,
+                                                     message, duration,
+                                                     callback, extra))
+            self._requestCondition.notify()
+
+    def registerHotkeys(self, hotkeys, callback, extra = None):
+        """Request registering the given hotkeys."""
+        with self._requestCondition:
+            self._requests.append(RegisterHotkeysRequest(self, hotkeys,
+                                                         callback, extra))
+            self._requestCondition.notify()
+
+    def requestHotkeysState(self, period, callback, extra = None):
+        """Request a periodic query of the hotkey status."""
+        with self._requestCondition:
+            id = self._nextPeriodicID
+            self._nextPeriodicID += 1
+            request = HotkeysStateRequest(self, id, period, callback, extra)
+            self._periodicRequests.append(request)
+            self._requestCondition.notify()
+            return id
+
+    def unregisterHotkeys(self, callback, extra = None):
+        """Request unregistering the hotkeys."""
+        with self._requestCondition:
+            self._requests.append(UnregisterHotkeysRequest(self,
+                                                           callback, extra))
+            self._requestCondition.notify()
+
+    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()>0:
+                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, attempts = 0):
+        """Try to connect to the flight simulator via XPLRA
+
+        Returns True if the connection has been established, False if it was
+        not due to no longer requested.
+        """
+        while self._connectionRequested:
+            if attempts>=self.NUM_CONNECTATTEMPTS:
+                self._connectionRequested = False
+                if autoReconnection:
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.disconnected())
+                else:
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.connectionFailed())
+                return 0
+
+            try:
+                attempts += 1
+                self._xplane.connect()
+
+                (xplaneVersion, xplmVersion, xplraVersion) = \
+                  self._xplane.getVersions()
+
+                description = "(X-Plane version: %d, XPLM version: %d, XPLRA version: %03d)" % \
+                  (xplaneVersion, xplmVersion, xplraVersion)
+                if not autoReconnection:
+                    fsType = const.SIM_XPLANE10 if xplaneVersion>=10000 else const.SIM_XPLANE9
+
+                    Handler._callSafe(lambda:
+                                      self._connectionListener.connected(fsType,
+                                                                         description))
+                self._connected = True
+                return attempts
+            except Exception, e:
+                print "xplane.Handler._connect: connection failed: " + \
+                      util.utf2unicode(str(e)) + \
+                      " (attempts: %d)" % (attempts,)
+                if attempts<self.NUM_CONNECTATTEMPTS:
+                    time.sleep(self.CONNECT_INTERVAL)
+                self._xplane.disconnect()
+
+    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 "xplane.Handler._disconnect"
+        if self._connected:
+            try:
+                self._xplane.disconnect()
+            except:
+                pass
+
+            self._connected = False
+
+    def _processRequest(self, request, time, attempts):
+        """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 "xplane.Handler._processRequest", request
+
+        needReconnect = False
+        try:
+            self._watchdogClient.set()
+            try:
+                if not request.process(time):
+                    print "xplane.Handler._processRequest: X-Plane returned invalid data too many times, reconnecting"
+                    needReconnect = True
+            except Exception as e:
+                print "xplane.Handler._processRequest: X-Plane connection failed (" + \
+                      util.utf2unicode(str(e)) + \
+                      "), reconnecting (attempts=%d)." % (attempts,)
+                needReconnect = True
+
+            if needReconnect:
+                with self._requestCondition:
+                    self._requests.insert(0, request)
+                self._disconnect()
+                return self._connect(autoReconnection = True, attempts = attempts)
+            else:
+                return 0
+        finally:
+            self._watchdogClient.clear()
+            self._requestCondition.acquire()
+
+    def _processRequests(self):
+        """Process any pending requests.
+
+        Will be called with the request lock held."""
+        attempts = 0
+        while self._connectionRequested and self._periodicRequests:
+            self._periodicRequests.sort()
+            request = self._periodicRequests[0]
+
+            t = time.time()
+
+            if request.nextFire>t:
+                break
+
+            attempts = self._processRequest(request, t, attempts)
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            attempts = self._processRequest(request, None, attempts)
+
+        return self._connectionRequested
+
+#------------------------------------------------------------------------------
+
+class Simulator(object):
+    """The simulator class representing the interface to the flight simulator
+    via XPLRA."""
+    # The basic data that should be queried all the time once we are connected
+    timeData = [ ("sim/time/local_date_days", TYPE_INT),
+                 ("sim/time/zulu_time_sec", TYPE_FLOAT) ]
+
+    normalData = timeData + \
+                 [ ("sim/aircraft/view/acf_tailnum", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_author", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_descrip", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_notes", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_ICAO", TYPE_BYTE_ARRAY),
+                   ("sim/aircraft/view/acf_livery_path", TYPE_BYTE_ARRAY) ]
+
+    flareData1 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
+                   ("sim/flightmodel/position/y_agl", TYPE_FLOAT),
+                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT) ]
+
+    flareStartData = [ ("sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
+                       ("sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
+                       ("sim/weather/visibility_reported_m", TYPE_FLOAT) ]
+
+    flareData2 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
+                   ("sim/flightmodel/failures/onground_any", TYPE_INT),
+                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
+                   ("sim/flightmodel/position/indicated_airspeed2",
+                    TYPE_FLOAT),
+                   ("sim/flightmodel/position/theta", TYPE_FLOAT),
+                   ("sim/flightmodel/position/phi", TYPE_FLOAT),
+                   ("sim/flightmodel/position/psi", TYPE_FLOAT) ]
+
+    TIME_SYNC_INTERVAL = 3.0
+
+    @staticmethod
+    def _getTimestamp(data):
+        """Convert the given data into a timestamp."""
+        year = datetime.date.today().year
+        timestamp = calendar.timegm(time.struct_time([year,
+                                                      1, 1, 0, 0, 0, -1, 1, 0]))
+        timestamp += data[0] * 24 * 3600
+        timestamp += data[1]
+
+        return timestamp
+
+    @staticmethod
+    def _getHotkeyCode(hotkey):
+        """Get the hotkey code for the given hot key."""
+        code = ord(hotkey.key)
+        if hotkey.shift: code |= HOTKEY_MODIFIER_SHIFT
+        if hotkey.ctrl: code |= HOTKEY_MODIFIER_CONTROL
+        return code
+
+    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._fsType = None
+        self._aircraft = None
+
+        self._handler = Handler(self,
+                                connectAttempts = connectAttempts,
+                                connectInterval = connectInterval)
+        self._connectionListener = connectionListener
+        self._handler.start()
+
+        self._syncTime = False
+        self._nextSyncTime = -1
+
+        self._normalRequestID = None
+
+        self._monitoringRequested = False
+        self._monitoring = False
+
+        self._aircraftInfo = None
+        self._aircraftModel = None
+
+        self._flareRequestID = None
+        self._flareRates = []
+        self._flareStart = None
+        self._flareStartFS = None
+
+        self._hotkeyLock = threading.Lock()
+        self._hotkeyCodes = None
+        self._hotkeySetID = 0
+        self._hotkeySetGeneration = 0
+        self._hotkeyOffets = None
+        self._hotkeyRequestID = None
+        self._hotkeyCallback = None
+
+        self._latin1decoder = codecs.getdecoder("iso-8859-1")
+        self._fuelCallback = None
+
+    def connect(self, aircraft):
+        """Initiate a connection to the simulator."""
+        self._aircraft = aircraft
+        self._aircraftInfo = 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."""
+        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT) ]
+        self._handler.requestRead(data, 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."""
+        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
+                 ("sim/flightmodel/weight/m_total", TYPE_FLOAT) ]
+        self._handler.requestRead(data, self._handleWeights,
+                                  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 handler 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 handler 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."""
+        print "xplra.Simulator.sendMessage:", message
+        self._handler.requestShowMessage(message, duration,
+                                         self._handleMessageSent,
+                                         extra = _disconnect)
+
+    def getFuel(self, callback):
+        """Get the fuel information for the current model.
+
+        The callback will be called with a list of triplets with the following
+        items:
+        - the fuel tank identifier
+        - the current weight of the fuel in the tank (in kgs)
+        - the current total capacity of the tank (in kgs)."""
+        if self._aircraftModel is None:
+            self._fuelCallback = callback
+        else:
+            self._aircraftModel.getFuel(self._handler, 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."""
+        if self._aircraftModel is not None:
+            self._aircraftModel.setFuelLevel(self._handler, levels)
+
+    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 listenHotkeys(self, hotkeys, callback):
+        """Start listening to the given hotkeys.
+
+        callback is function expecting two arguments:
+        - the ID of the hotkey set as returned by this function,
+        - the list of the indexes of the hotkeys that were pressed."""
+        with self._hotkeyLock:
+            assert self._hotkeyCodes is None
+
+            self._hotkeyCodes = \
+              [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
+            self._hotkeySetID += 1
+            self._hotkeySetGeneration = 0
+            self._hotkeyCallback = callback
+
+            self._handler.registerHotkeys(self._hotkeyCodes,
+                                          self._handleHotkeysRegistered,
+                                          (self._hotkeySetID,
+                                           self._hotkeySetGeneration))
+
+            return self._hotkeySetID
+
+    def clearHotkeys(self):
+        """Clear the current hotkey set.
+
+        Note that it is possible, that the callback function set either
+        previously or after calling this function by listenHotkeys() will be
+        called with data from the previous hotkey set.
+
+        Therefore it is recommended to store the hotkey set ID somewhere and
+        check that in the callback function. Right before calling
+        clearHotkeys(), this stored ID should be cleared so that the check
+        fails for sure."""
+        with self._hotkeyLock:
+            if self._hotkeyCodes is not None:
+                self._hotkeyCodes = None
+                self._hotkeySetID += 1
+                self._hotkeyCallback = None
+                self._clearHotkeyRequest()
+
+    def disconnect(self, closingMessage = None, duration = 3):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested
+
+        print "xplra.Simulator.disconnect", closingMessage, duration
+
+        self._stopNormal()
+        self.clearHotkeys()
+        if closingMessage is None:
+            self._handler.disconnect()
+        else:
+            self.sendMessage(closingMessage, duration = duration,
+                             _disconnect = True)
+
+    def connected(self, fsType, descriptor):
+        """Called when a connection has been established to the flight
+        simulator of the given type."""
+        self._fsType = fsType
+
+        with self._hotkeyLock:
+            if self._hotkeyCodes is not None:
+                self._hotkeySetGeneration += 1
+
+                self._handler.registerHotkeys(self._hotkeyCodes,
+                                              self._handleHotkeysRegistered,
+                                              (self._hotkeySetID,
+                                               self._hotkeySetGeneration))
+
+        self._connectionListener.connected(fsType, descriptor)
+
+    def connectionFailed(self):
+        """Called when the connection could not be established."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.connectionFailed()
+
+    def disconnected(self):
+        """Called when a connection to the flight simulator has been broken."""
+        with self._hotkeyLock:
+            self._clearHotkeyRequest()
+        self._connectionListener.disconnected()
+
+    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)
+
+    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 _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.getString(2),
+                                                data.getString(3),
+                                                data.getString(4),
+                                                data.getString(5),
+                                                data.getString(6),
+                                                data.getString(7))
+        if self._fuelCallback is not None:
+            self._aircraftModel.getFuel(self._handler, self._fuelCallback)
+            self._fuelCallback = None
+
+        if self._monitoringRequested and not self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+        elif self._monitoring and not self._monitoringRequested:
+            self._stopNormal()
+            self._startDefaultNormal()
+        elif self._monitoring and self._aircraftModel is not None and \
+             not createdNewModel:
+            aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
+                                                                 timestamp, data)
+
+            self._aircraft.handleState(aircraftState)
+
+    def _setAircraftName(self, timestamp, tailnum, author, description,
+                         notes, icao, liveryPath):
+        """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."""
+        author = self._latin1decoder(author)[0]
+        description = self._latin1decoder(description)[0]
+        notes = self._latin1decoder(notes)[0]
+        liveryPath = self._latin1decoder(liveryPath)[0]
+
+        aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
+        if aircraftInfo==self._aircraftInfo:
+            return False
+
+        print "xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
+              (tailnum, author, description, notes, icao, liveryPath)
+
+        self._aircraftInfo = aircraftInfo
+        needNew = self._aircraftModel is None
+        needNew = needNew or\
+            not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
+        if not needNew:
+            specialModel = AircraftModel.findSpecial(self._aircraft,
+                                                     aircraftInfo)
+            needNew = specialModel is not None and \
+                specialModel is not self._aircraftModel.__class__
+
+        if needNew:
+            self._setAircraftModel(AircraftModel.create(self._aircraft,
+                                                        aircraftInfo))
+
+        self._aircraft.modelChanged(timestamp, description,
+                                    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
+        model.simulator = self
+
+        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._fsType)
+
+        self._normalRequestID = \
+            self._handler.requestPeriodicRead(1.0, data,
+                                              self._handleNormal)
+        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(data)
+
+    def _handleFlare1(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare1: " + str(data))
+        if data[1]<=50.0*0.3048:
+            self._flareStart = time.time()
+            self._flareStartFS = data[0]
+            self._handler.clearPeriodic(self._flareRequestID)
+            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]
+            if windDirection<0.0: windDirection += 360.0
+            self._aircraft.flareStarted(data[0], windDirection, data[2],
+                                        self._flareStart, self._flareStartFS)
+
+        self._flareRequestID = \
+          self._handler.requestPeriodicRead(0.1,
+                                            Simulator.flareData2,
+                                            self._handleFlare2)
+
+    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 += 86400.0
+
+            tdRate = min(self._flareRates)
+            tdRateCalculatedByFS = False
+
+            heading = data[6]
+            if heading<0.0: heading += 360.0
+
+            self._aircraft.flareFinished(flareEnd, flareEndFS,
+                                         tdRate, tdRateCalculatedByFS,
+                                         data[3], data[4], data[5], heading)
+        else:
+            self._addFlareRate(data[2])
+
+    def _handleZFW(self, data, callback):
+        """Callback for a ZFW retrieval request."""
+        zfw = data[0] + data[1]
+        callback(zfw)
+
+    def _handleTime(self, data, callback):
+        """Callback for a time retrieval request."""
+        callback(Simulator._getTimestamp(data))
+
+    def _handleWeights(self, data, callback):
+        """Callback for the weights retrieval request."""
+        dow = data[0]
+        payload = data[1]
+        zfw = dow + payload
+        grossWeight = data[2]
+        callback(dow, payload, zfw, grossWeight)
+
+    def _handleMessageSent(self, success, disconnect):
+        """Callback for a message sending request."""
+        #print "xplra.Simulator._handleMessageSent", disconnect
+        if disconnect:
+            self._handler.disconnect()
+
+    def _handleHotkeysRegistered(self, success, (id, generation)):
+        """Handle the result of the hotkeys having been written."""
+        with self._hotkeyLock:
+            if success and id==self._hotkeySetID and \
+            generation==self._hotkeySetGeneration:
+                self._hotkeyRequestID = \
+                  self._handler.requestHotkeysState(0.5,
+                                                    self._handleHotkeys,
+                                                    (id, generation))
+
+    def _handleHotkeys(self, data, (id, generation)):
+        """Handle the hotkeys."""
+        with self._hotkeyLock:
+            if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
+                return
+
+            callback = self._hotkeyCallback
+            offsets = self._hotkeyOffets
+
+        hotkeysPressed = []
+        for i in range(0, len(data)):
+            if data[i]:
+                hotkeysPressed.append(i)
+
+        if hotkeysPressed:
+            callback(id, hotkeysPressed)
+
+    def _clearHotkeyRequest(self):
+        """Clear the hotkey request in the handler if there is any."""
+        if self._hotkeyRequestID is not None:
+            self._handler.unregisterHotkeys(self._hotkeysUnregistered)
+            self._handler.clearPeriodic(self._hotkeyRequestID)
+            self._hotkeyRequestID = None
+
+    def _hotkeysUnregistered(self, result, extra):
+        """Called when the hotkeys have been unregistered."""
+        pass
+
+#------------------------------------------------------------------------------
+
+class AircraftModel(object):
+    """Base class for the aircraft models.
+
+    Aircraft models handle the data arriving from X-Plane and turn it into an
+    object describing the aircraft's state."""
+    monitoringData = [ ("paused",
+                        "sim/time/paused", TYPE_INT),
+                       ("latitude",
+                        "sim/flightmodel/position/latitude", TYPE_DOUBLE),
+                       ("longitude",
+                        "sim/flightmodel/position/longitude", TYPE_DOUBLE),
+                       ("replay",
+                        "sim/operation/prefs/replay_mode", TYPE_INT),
+                       ("overspeed",
+                        "sim/flightmodel/failures/over_vne", TYPE_INT),
+                       ("stalled",
+                        "sim/flightmodel/failures/stallwarning", TYPE_INT),
+                       ("onTheGround",
+                        "sim/flightmodel/failures/onground_any", TYPE_INT),
+                       ("emptyWeight",
+                        "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
+                       ("payloadWeight",
+                        "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
+                       ("grossWeight",
+                        "sim/flightmodel/weight/m_total", TYPE_FLOAT),
+                       ("heading",
+                        "sim/flightmodel/position/psi", TYPE_FLOAT),
+                       ("pitch",
+                        "sim/flightmodel/position/theta", TYPE_FLOAT),
+                       ("bank",
+                        "sim/flightmodel/position/phi", TYPE_FLOAT),
+                       ("ias",
+                        "sim/flightmodel/position/indicated_airspeed2",
+                        TYPE_FLOAT),
+                       ("mach",
+                        "sim/flightmodel/misc/machno", TYPE_FLOAT),
+                       ("groundSpeed",
+                        "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
+                       ("vs",
+                        "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
+                       ("radioAltitude",
+                        "sim/flightmodel/position/y_agl", TYPE_FLOAT),
+                       ("altitude",
+                        "sim/flightmodel/position/elevation", TYPE_FLOAT),
+                       ("gLoad",
+                        "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
+                       ("flapsControl",
+                        "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
+                       ("flapsLeft",
+                        "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
+                       ("flapsRight",
+                        "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
+                       ("navLights",
+                        "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
+                       ("beaconLights",
+                        "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
+                       ("strobeLights",
+                        "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
+                       ("landingLights",
+                        "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
+                       ("pitot",
+                        "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
+                       ("parking",
+                        "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
+                       ("gearControl",
+                        "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
+                       ("noseGear",
+                        "sim/flightmodel2/gear/deploy_ratio",
+                        (TYPE_FLOAT_ARRAY, 1)),
+                       ("spoilers",
+                        "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
+                       ("altimeter",
+                        "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
+                       ("qnh",
+                        "sim/physics/earth_pressure_p", TYPE_FLOAT),
+                       ("nav1",
+                        "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
+                       ("nav1_obs",
+                        "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
+                       ("nav2",
+                        "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
+                       ("nav2_obs",
+                        "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
+                       ("adf1",
+                        "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
+                       ("adf2",
+                        "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
+                       ("squawk",
+                        "sim/cockpit/radios/transponder_code", TYPE_INT),
+                       ("windSpeed",
+                        "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
+                       ("windDirection",
+                        "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
+                       ("visibility",
+                        "sim/weather/visibility_reported_m", TYPE_FLOAT),
+                       ("cog",
+                        "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
+                       ("xpdrC",
+                        "sim/cockpit/radios/transponder_mode", TYPE_INT),
+                       ("apMaster",
+                        "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
+                       ("apState",
+                        "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
+                       ("apHeading",
+                        "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
+                       ("apAltitude",
+                        "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
+                       ("elevatorTrim",
+                        "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
+                       ("antiIceOn",
+                        "sim/cockpit/switches/anti_ice_on", TYPE_INT),
+                       ("surfaceHeat",
+                        "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
+                       ("propHeat",
+                        "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
+                       ("autopilotOn",
+                        "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
+                       ("apHeadingMode",
+                        "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
+
+
+    specialModels = []
+
+    @staticmethod
+    def registerSpecial(clazz):
+        """Register the given class as a special model."""
+        AircraftModel.specialModels.append(clazz)
+
+    @staticmethod
+    def findSpecial(aircraft, aircraftInfo):
+        for specialModel in AircraftModel.specialModels:
+            if specialModel.doesHandle(aircraft, aircraftInfo):
+                return specialModel
+        return None
+
+    @staticmethod
+    def create(aircraft, aircraftInfo):
+        """Create the model for the given aircraft name, and notify the
+        aircraft about it."""
+        specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
+        if specialModel is not None:
+            return specialModel()
+        if aircraft.type in _genericModels:
+            return _genericModels[aircraft.type]()
+        else:
+            return GenericModel()
+
+    @staticmethod
+    def _convertFrequency(value):
+        """Convert the given frequency value into a string."""
+        return "%.2f" % (value/100.0,)
+
+    @staticmethod
+    def _convertOBS(value):
+        """Convert the given OBS value into an integer."""
+        while value<0.0:
+            value += 360.0
+        return int(round(value))
+
+    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
+        self._simulator = None
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic"
+
+    @property
+    def simulator(self):
+        """Get the simulator this aircraft model works for."""
+        return self._simulator
+
+    @simulator.setter
+    def simulator(self, simulator):
+        """Get the simulator this aircraft model works for."""
+        self._simulator = simulator
+
+    def doesHandle(self, aircraft, aircraftInfo):
+        """Determine if the model handles the given aircraft name.
+
+        This default implementation returns False."""
+        return False
+
+    def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
+        """Add the given X-Plane dataref name and type to the given array and a
+        member attribute with the given name."""
+        dest.append((name, type))
+        if attrName is not None:
+            setattr(self, attrName, len(dest)-1)
+
+    def _addDataWithIndexMembers(self, dest, prefix, data):
+        """Add X-Plane dataref 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 X-Plane dataref name
+        - the dataref type
+
+        The latter two items will be appended to dest."""
+        for (name, datarefName, type) in data:
+            self._addDatarefWithIndexMember(dest, datarefName, type,
+                                            prefix + name)
+
+    def addMonitoringData(self, data, fsType):
+        """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()
+
+        lnavOn = data[self._monidx_autopilotOn]!=0 and \
+                 data[self._monidx_apHeadingMode]==2
+
+        state.timestamp = timestamp
+
+        state.latitude = data[self._monidx_latitude]
+        state.longitude = data[self._monidx_longitude]
+        if state.longitude>180.0: state.longitude = 360.0 - state.longitude
+
+        state.paused = data[self._monidx_paused]!=0 or \
+            data[self._monidx_replay]!=0
+        state.trickMode = data[self._monidx_replay]!=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_emptyWeight] + \
+                    data[self._monidx_payloadWeight]
+        state.grossWeight = data[self._monidx_grossWeight]
+
+        state.heading = data[self._monidx_heading]
+
+        state.pitch = -1.0 * data[self._monidx_pitch]
+        state.bank = data[self._monidx_bank]
+
+        state.ias = data[self._monidx_ias]
+        state.mach = data[self._monidx_mach]
+        state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
+        state.vs = data[self._monidx_vs]
+
+        state.radioAltitude = data[self._monidx_radioAltitude]/.3048
+        state.altitude = data[self._monidx_altitude]/.3048
+
+        state.gLoad = data[self._monidx_gLoad]
+
+        flapsControl = data[self._monidx_flapsControl]
+        flapsIndex = int(flapsControl * (len(self._flapsNotches)-1))
+        state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
+
+        state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
+
+        state.navLightsOn = data[self._monidx_navLights] != 0
+        state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
+        state.landingLightsOn = data[self._monidx_landingLights] != 0
+        state.strobeLightsOn = data[self._monidx_strobeLights] != 0
+
+        state.pitotHeatOn = data[self._monidx_pitot]!=0
+
+        state.parking = data[self._monidx_parking]>=0.5
+
+        state.gearControlDown = data[self._monidx_gearControl]!=0
+        state.gearsDown = data[self._monidx_noseGear][0]>0.99
+
+        state.spoilersArmed = None
+
+        state.spoilersExtension = data[self._monidx_spoilers]*100.0
+
+        state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
+        state.altimeterReliable = True
+        state.qnh = data[self._monidx_qnh]/100.0
+
+        state.ils = None
+        state.ils_obs = None
+        state.ils_manual = False
+        state.nav1 = self._convertFrequency(data[self._monidx_nav1])
+        state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
+        state.nav1_manual = True
+        state.nav2 = self._convertFrequency(data[self._monidx_nav2])
+        state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
+        state.nav2_manual = not lnavOn
+        state.adf1 = str(data[self._monidx_adf1])
+        state.adf2 = str(data[self._monidx_adf2])
+
+        state.squawk = "%04d" % (data[self._monidx_squawk],)
+
+        state.windSpeed = data[self._monidx_windSpeed]
+        state.windDirection = data[self._monidx_windDirection]
+        if state.windDirection<0.0: state.windDirection += 360.0
+
+        state.visibility = data[self._monidx_visibility]
+
+        state.cog = data[self._monidx_cog]
+
+        state.xpdrC = data[self._monidx_xpdrC]==2
+        state.autoXPDR = False
+
+        state.apMaster = data[self._monidx_apMaster]==2
+        apState = data[self._monidx_apState]
+        if lnavOn:
+           state.apHeadingHold = None
+           state.apHeading = None
+        else:
+            state.apHeadingHold = (apState&0x00002)!=0
+            state.apHeading = data[self._monidx_apHeading]
+
+        state.apAltitudeHold = (apState&0x04000)!=0
+        state.apAltitude = data[self._monidx_apAltitude]
+
+        state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
+
+        state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
+                          data[self._monidx_surfaceHeat]!=0 or \
+                          data[self._monidx_propHeat]!=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._fuelTankCapacities = [1.0] * len(fuelTanks)
+        self._fuelIndex = None
+        self._numEngines = numEngines
+        self._engineStartIndex = None
+        self._isN1 = isN1
+
+    def doesHandle(self, aircraft, aircraftInfo):
+        """Determine if the model handles the given aircraft name.
+
+        This implementation returns True."""
+        return True
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        super(GenericAircraftModel, self).addMonitoringData(data, fsType)
+
+        self._fuelIndex = self._addFuelData(data)
+
+        self._engineStartIndex = len(data)
+        if self._isN1:
+            self._addDatarefWithIndexMember(data,
+                                             "sim/flightmodel/engine/ENGN_N1_",
+                                             (TYPE_FLOAT_ARRAY,
+                                              self._numEngines))
+        else:
+            self._addDatarefWithIndexMember(data,
+                                            "sim/flightmodel/engine/POINT_tacrad",
+                                            (TYPE_FLOAT_ARRAY,
+                                             self._numEngines))
+
+        self._addDatarefWithIndexMember(data,
+                                        "sim/flightmodel/engine/ENGN_propmode",
+                                        (TYPE_INT_ARRAY, self._numEngines))
+
+    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)
+
+        state.fuel = []
+        state.totalFuel = 0.0
+
+        fuelAmounts = data[self._fuelIndex]
+        for i in range(0, len(self._fuelTanks)):
+            amount = fuelAmounts[i]
+            state.fuel.append((self._fuelTanks[i], amount))
+            state.totalFuel += amount
+
+        power = data[self._engineStartIndex]
+
+        state.n1 = power[:] if self._isN1 else None
+        state.rpm = None if self._isN1 else power[:]
+
+        propMode = data[self._engineStartIndex+1]
+        state.reverser = [mode == 3 for mode in propMode]
+
+        return state
+
+    def getFuel(self, handler, callback):
+        """Get the fuel information for this model.
+
+        See Simulator.getFuel for more information. This
+        implementation simply queries the fuel tanks given to the
+        constructor."""
+        data = []
+        self._addFuelData(data)
+        data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
+        data.append( ("sim/aircraft/overflow/acf_tank_rat",
+                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
+
+        handler.requestRead(data, self._handleFuelRetrieved,
+                            extra = callback)
+
+    def setFuelLevel(self, handler, levels):
+        """Set the fuel level.
+
+        See the description of Simulator.setFuelLevel. This
+        implementation simply sets the fuel tanks as given."""
+        data = []
+        for (tank, level) in levels:
+            try:
+                index = self._fuelTanks.index(tank)
+                data.append( ("sim/flightmodel/weight/m_fuel",
+                              (TYPE_FLOAT_ARRAY, 1, index),
+                              [level * self._fuelTankCapacities[index]]) )
+            except:
+                print "xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
+                  (tank,)
+
+        handler.requestWrite(data, self._handleFuelWritten)
+
+    def _addFuelData(self, data):
+        """Add the fuel offsets to the given data array.
+
+        Returns the index of the first fuel tank's data."""
+        fuelStartIndex = len(data)
+        data.append( ("sim/flightmodel/weight/m_fuel",
+                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
+
+        return fuelStartIndex
+
+    def _convertFuelData(self, data, index = 0, addCapacities = False):
+        """Convert the given data into a fuel info list.
+
+        The list consists of two or three-tuples of the following
+        items:
+        - the fuel tank ID,
+        - the amount of the fuel in kg,
+        - if addCapacities is True, the total capacity of the tank."""
+        fuelWeight = data[index] / 256.0
+        index += 1
+
+        result = []
+        totalFuel = 0
+        for fuelTank in self._fuelTanks:
+            capacity = data[index+1] * fuelWeight * const.LBSTOKG
+            if capacity>=1.0:
+                amount = data[index] * capacity / 128.0 / 65536.0
+
+                result.append( (fuelTank, amount, capacity) if addCapacities
+                               else (fuelTank, amount))
+                totalFuel += amount
+            index += 2
+
+        return (result, totalFuel)
+
+    def _handleFuelRetrieved(self, data, callback):
+        """Callback for a fuel retrieval request."""
+        result = []
+        totalCapacity = data[1]
+        for index in range(0, len(self._fuelTanks)):
+            amount = data[0][index]
+            capacity = data[2][index] * totalCapacity
+            self._fuelTankCapacities[index] = capacity
+            result.append( (self._fuelTanks[index], amount, capacity) )
+
+        callback(result)
+
+    def _handleFuelWritten(self, success, extra):
+        """Callback for a fuel setting request."""
+        pass
+
+#------------------------------------------------------------------------------
+
+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 "X-Plane/Generic"
+
+#------------------------------------------------------------------------------
+
+class B737Model(GenericAircraftModel):
+    """Generic model for the Boeing 737 Classing and NG aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B737Model, self). \
+            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
+                     fuelTanks = B737Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Boeing 737"
+
+#------------------------------------------------------------------------------
+
+class B767Model(GenericAircraftModel):
+    """Generic model for the Boeing 767 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(B767Model, self). \
+            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
+                     fuelTanks = B767Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Boeing 767"
+
+#------------------------------------------------------------------------------
+
+class DH8DModel(GenericAircraftModel):
+    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DH8DModel, self). \
+            __init__(flapsNotches = [0, 5, 10, 15, 35],
+                     fuelTanks = DH8DModel.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Bombardier Dash 8-Q400"
+
+#------------------------------------------------------------------------------
+
+class FJSDH8DModel(DH8DModel):
+    """Model handler for the FlyJSim Dash 8-Q400."""
+    @staticmethod
+    def doesHandle(aircraft, (tailnum, author, description, notes,
+                              icao, liveryPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type==const.AIRCRAFT_DH8D and \
+          description.find("Dash 8 Q400")!=-1 and \
+          ((author in ["2012", "2013"] and tailnum=="N62890") or \
+           author.find("Jack Skieczius")!=-1)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
+
+    def addMonitoringData(self, data, fsType):
+        """Add the model-specific monitoring data to the given array."""
+        super(FJSDH8DModel, self).addMonitoringData(data, fsType)
+
+        self._speedBrakeIndex = len(data)
+        self._addDatarefWithIndexMember(data,
+                                        "sim/flightmodel2/wing/speedbrake1_deg",
+                                        (TYPE_FLOAT_ARRAY, 2))
+        self._addDatarefWithIndexMember(data,
+                                        "sim/flightmodel2/wing/speedbrake2_deg",
+                                        (TYPE_FLOAT_ARRAY, 2))
+
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then invert the pitot heat state."""
+        state = super(FJSDH8DModel, self).getAircraftState(aircraft,
+                                                           timestamp,
+                                                           data)
+        state.antiCollisionLightsOn = \
+          state.antiCollisionLightsOn or state.strobeLightsOn
+        state.cog = (state.cog / 0.0254 + 21.504) / 94.512
+
+        # It seems that N1 does not always go down to 0 properly
+        # (maybe due to winds?)
+        state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
+
+        state.spoilersExtension = \
+            sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class CRJ2Model(GenericAircraftModel):
+    """Generic model for the Bombardier CRJ-200 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(CRJ2Model, self). \
+            __init__(flapsNotches = [0, 8, 20, 30, 45],
+                     fuelTanks = CRJ2Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Bombardier CRJ-200"
+
+#------------------------------------------------------------------------------
+
+class F70Model(GenericAircraftModel):
+    """Generic model for the Fokker F70 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(F70Model, self). \
+            __init__(flapsNotches = [0, 8, 15, 25, 42],
+                     fuelTanks = F70Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Fokker 70"
+
+#------------------------------------------------------------------------------
+
+class DC3Model(GenericAircraftModel):
+    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT]
+    # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+    #              const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(DC3Model, self). \
+            __init__(flapsNotches = [0, 15, 30, 45],
+                     fuelTanks = DC3Model.fuelTanks,
+                     numEngines = 2, isN1 = False)
+        self._leftLevel = 0.0
+        self._rightLevel = 0.0
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Lisunov Li-2 (DC-3)"
+
+#------------------------------------------------------------------------------
+
+class T134Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
+                 const.FUELTANK_LEFT_AUX,
+                 const.FUELTANK_CENTRE,
+                 const.FUELTANK_RIGHT_AUX,
+                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T134Model, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelTanks = T134Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Tupolev Tu-134"
+
+#------------------------------------------------------------------------------
+
+class T154Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
+                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
+                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
+
+    def __init__(self):
+        """Construct the model."""
+        super(T154Model, self). \
+            __init__(flapsNotches = [0, 15, 28, 45],
+                     fuelTanks = T154Model.fuelTanks,
+                     numEngines = 3)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/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."""
+    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
+
+    def __init__(self):
+        """Construct the model."""
+        super(YK40Model, self). \
+            __init__(flapsNotches = [0, 20, 35],
+                     fuelTanks = YK40Model.fuelTanks,
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "X-Plane/Generic Yakovlev Yak-40"
+
+#------------------------------------------------------------------------------
+
+_genericModels = { const.AIRCRAFT_B736  : B737Model,
+                   const.AIRCRAFT_B737  : B737Model,
+                   const.AIRCRAFT_B738  : B737Model,
+                   const.AIRCRAFT_B738C : 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  : CRJ2Model,
+                   const.AIRCRAFT_F70   : F70Model,
+                   const.AIRCRAFT_DC3   : DC3Model,
+                   const.AIRCRAFT_T134  : T134Model,
+                   const.AIRCRAFT_T154  : T154Model,
+                   const.AIRCRAFT_YK40  : YK40Model }
+
+#------------------------------------------------------------------------------
+
+AircraftModel.registerSpecial(FJSDH8DModel)
+
+#------------------------------------------------------------------------------
+
+# if __name__ == "__main__":
+#     class ConnectionListener:
+#         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 Config:
+#         def __init__(self):
+#             self.onlineACARS = False
+#             self.realIASSmoothingLength = 2
+#             self.realVSSmoothingLength = 2
+#             self.enableSounds = False
+#             self.usingFS2Crew = False
+
+#         def isMessageTypeFS(self, type):
+#             return True
+
+
+
+#     class GUI:
+#         def __init__(self):
+#             self.config = Config()
+#             self.entranceExam = False
+#             self.zfw = 30000.0
+
+#         def resetFlightStatus(self):
+#             pass
+
+#         def setRating(self, value):
+#             pass
+
+#         def insertFlightLogLine(self, index, ts, text, isFault):
+#             pass
+
+#         def setStage(self, stage):
+#             pass
+
+
+#     from i18n import setLanguage
+
+#     setLanguage("/home/vi/munka/repules/mlx", "en")
+
+#     from logger import Logger
+#     from flight import Flight
+#     from acft import DH8D
+
+#     gui = GUI()
+
+#     logger = Logger(gui)
+
+#     flight = Flight(logger, gui)
+#     acft = DH8D(flight)
+
+#     Watchdog()
+
+#     connectionListener = ConnectionListener()
+#     simulator = Simulator(connectionListener, connectAttempts = 3)
+
+#     simulator.connect(acft)
+
+#     time.sleep(2)
+
+#     simulator.startMonitoring()
+
+#     simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
+
+#     time.sleep(4)
+
+#     simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116, R117, R210, R211, R212, R212A, R220, R221, R222, R223, R224, R225, R226, R227, R270, R271, R272, R274, R275, R276, R277, R278, R278A, R279", duration = 20)
+#     #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116", duration = 20)
+#     #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115", duration = 20)
+
+#     time.sleep(30)
+
+#     simulator.sendMessage("[MLX] Hello", duration = 3)
+
+#     time.sleep(10)
Index: src/mlx/xplane.py
===================================================================
--- src/mlx/xplane.py	(revision 642)
+++ 	(revision )
@@ -1,2014 +1,0 @@
-
-import fs
-import const
-import util
-from watchdog import Watchdog
-
-import threading
-import time
-import calendar
-import datetime
-import sys
-import codecs
-import math
-
-from xplra import XPlane, MultiGetter, MultiSetter, ProtocolException
-from xplra import TYPE_INT, TYPE_FLOAT, TYPE_DOUBLE
-from xplra import TYPE_FLOAT_ARRAY, TYPE_INT_ARRAY, TYPE_BYTE_ARRAY
-from xplra import HOTKEY_MODIFIER_SHIFT, HOTKEY_MODIFIER_CONTROL
-
-#------------------------------------------------------------------------------
-
-## @package mlx.xplane
-#
-# The module towards X-Plane
-#
-# This module implements the simulator interface to X-Plane via the
-# X-Plane Remote Access (xplra) plugin.
-
-#------------------------------------------------------------------------------
-
-_hgin2hpa = 1013.25 / 29.92
-
-_mps2knots = 3600.0 / 1852
-
-#------------------------------------------------------------------------------
-
-class Request(object):
-    """Base class for one-shot requests."""
-    def __init__(self, handler, callback, extra):
-        """Construct the request."""
-        self._handler = handler
-        self._callback = callback
-        self._extra = extra
-        self._result = None
-
-    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._process(time):
-            Handler._callSafe(lambda: self._callback(self._result,
-                                                     self._extra))
-            return True
-        else:
-            return False
-
-    def fail(self):
-        """Handle the failure of this request."""
-        Handler._callSafe(lambda: self._callback(False, self._extra))
-
-class DataRequest(Request):
-    """A simple, one-shot data read or write request."""
-    def __init__(self, handler, forWrite, data, callback, extra,
-                 validator = None):
-        """Construct the request."""
-        super(DataRequest, self).__init__(handler, callback, extra)
-
-        self._forWrite = forWrite
-        self._validator = validator
-
-        xplane = handler._xplane
-        self._multiBuffer = xplane.createMultiSetter() if forWrite \
-                            else xplane.createMultiGetter()
-
-        Handler._setupMultiBuffer(self._multiBuffer,
-                                  [(d[0], d[1]) for d in data])
-
-        if forWrite:
-            index = 0
-            for (_, _, value) in data:
-                self._multiBuffer[index] = value
-                index += 1
-
-
-    def fail(self):
-        """Handle the failure of this request."""
-        if self._forWrite:
-            super(DataRequest, self).fail()
-        else:
-            Handler._callSafe(lambda: self._callback(None, self._extra))
-
-    def _process(self, time):
-        """Process the request."""
-        if self._forWrite:
-            self._multiBuffer.execute()
-            self._result = True
-            return True
-
-        try:
-            if Handler._performRead(self._multiBuffer,
-                                    self._extra, self._validator):
-                self._result = self._multiBuffer
-                return True
-            else:
-                return False
-        except ProtocolException, e:
-            self._result = None
-            return True
-
-class ShowMessageRequest(Request):
-    """Request to show a message in the simulator window."""
-    def __init__(self, handler, message, duration, callback, extra):
-        """Construct the request."""
-        super(ShowMessageRequest, self).__init__(handler,
-                                                         callback, extra)
-        self._message = message
-        self._duration = duration
-
-    def _process(self, time):
-        """Process the request."""
-        self._handler._xplane.showMessage(self._message, self._duration)
-        self._result = True
-        return True
-
-class RegisterHotkeysRequest(Request):
-    """Request to register hotkeys with the simulator."""
-    def __init__(self, handler, hotkeyCodes, callback, extra):
-        """Construct the request."""
-        super(RegisterHotkeysRequest, self).__init__(handler,
-                                                     callback,
-                                                     extra)
-        self._hotkeyCodes = hotkeyCodes
-
-    def _process(self, time):
-        """Process the request."""
-        self._handler._xplane.registerHotkeys(self._hotkeyCodes)
-        self._result = True
-        return True
-
-class UnregisterHotkeysRequest(Request):
-    """Request to register hotkeys with the simulator."""
-    def _process(self, time):
-        """Process the request."""
-        self._handler._xplane.unregisterHotkeys()
-        self._result = True
-        return True
-
-class PeriodicRequest(object):
-    """A periodic request."""
-    def __init__(self, handler, id, period, callback, extra):
-        """Construct the periodic request."""
-        self._handler = handler
-        self._id = id
-        self._period = period
-        self._nextFire = time.time()
-        self._callback = callback
-        self._extra = extra
-        self._result = None
-
-    @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, now):
-        """Check if this request should be executed, and if so, do so.
-
-        now 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 now<self._nextFire:
-            return True
-
-        isOK = self._process(time)
-
-        if isOK:
-            Handler._callSafe(lambda: self._callback(self._result,
-                                                     self._extra))
-            now = time.time()
-            while self._nextFire <= now:
-                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)
-
-class PeriodicDataRequest(PeriodicRequest):
-    """A periodic request."""
-    def __init__(self, handler, id, period, data, callback, extra,
-                 validator):
-        """Construct the periodic request."""
-        super(PeriodicDataRequest, self).__init__(handler, id, period,
-                                                  callback, extra)
-        self._validator = validator
-        self._multiGetter = handler._xplane.createMultiGetter()
-        Handler._setupMultiBuffer(self._multiGetter, data)
-
-    def _process(self, now):
-        """Process the request."""
-        if Handler._performRead(self._multiGetter,
-                                self._extra, self._validator):
-            self._result = self._multiGetter
-            return True
-        else:
-            return False
-
-#------------------------------------------------------------------------------
-
-class HotkeysStateRequest(PeriodicRequest):
-    """Periodic hotkey query request."""
-    def _process(self, now):
-        """Process the request."""
-        self._result = self._handler._xplane.queryHotkeys()
-        return True
-
-#------------------------------------------------------------------------------
-
-class Handler(threading.Thread):
-    """The thread to handle the requests towards X-Plane."""
-    @staticmethod
-    def _callSafe(fun):
-        """Call the given function and swallow any exceptions."""
-        try:
-            return fun()
-        except Exception, e:
-            print >> sys.stderr, util.utf2unicode(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 _setupMultiBuffer(buffer, dataSpec):
-        """Setup the given multi-dataref buffer for the given data
-        specification.
-
-        The specification is a list of tuples of two items:
-        - the name of the dataref
-        - the type of the dataref. It can be one of the following:
-          - an integer denoting the type. If it denotes an array type, the
-            length will be -1 (i.e. as many as returned when reading the
-            value), and the offset will be 0
-          - a tuple of two or three items:
-            - the first item is the type constant
-            - the second item is the length
-            - the third item is the offset, which defaults to 0."""
-        for (name, typeInfo) in dataSpec:
-            length = -1
-            offset = 0
-            type = 0
-            if isinstance(typeInfo, tuple):
-                type = typeInfo[0]
-                length = typeInfo[1]
-                offset = 0 if len(typeInfo)<3 else typeInfo[2]
-            else:
-                type = typeInfo
-
-            if type==TYPE_INT:
-                buffer.addInt(name)
-            elif type==TYPE_FLOAT:
-                buffer.addFloat(name)
-            elif type==TYPE_DOUBLE:
-                buffer.addDouble(name)
-            elif type==TYPE_FLOAT_ARRAY:
-                buffer.addFloatArray(name, length = length, offset = offset)
-            elif type==TYPE_INT_ARRAY:
-                buffer.addIntArray(name, length = length, offset = offset)
-            elif type==TYPE_BYTE_ARRAY:
-                buffer.addByteArray(name, length = length, offset = offset)
-            else:
-                raise TypeError("xplane.Handler._setupMultiBuffer: invalid type info: %s for dataref '%s'" % (typeInfo, name))
-
-
-
-    @staticmethod
-    def _performRead(multiGetter, 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:
-            try:
-                multiGetter.execute()
-            except ProtocolException, e:
-                print "xplane.Handler._performRead: " + str(e)
-                raise
-
-            if validator is None or \
-               Handler._callSafe(lambda: validator(multiGetter, extra)):
-                return True
-            else:
-                attemptsLeft -= 1
-        return False
-
-    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._xplane = XPlane()
-
-        self._requestCondition = threading.Condition()
-        self._connectionRequested = False
-        self._connected = False
-
-        self._requests = []
-        self._nextPeriodicID = 1
-        self._periodicRequests = []
-
-        self._watchdogClient = Watchdog.get().addClient(2.0, "xplane.Handler")
-
-        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(DataRequest(self, 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 = DataRequest(self, True, data, callback, extra)
-            #print "xplane.Handler.requestWrite", request
-            self._requests.append(request)
-            self._requestCondition.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 = PeriodicDataRequest(self, 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 requestShowMessage(self, message, duration, callback, extra = None):
-        """Request showing a message in the simulator."""
-        with self._requestCondition:
-            self._requests.append(ShowMessageRequest(self,
-                                                     message, duration,
-                                                     callback, extra))
-            self._requestCondition.notify()
-
-    def registerHotkeys(self, hotkeys, callback, extra = None):
-        """Request registering the given hotkeys."""
-        with self._requestCondition:
-            self._requests.append(RegisterHotkeysRequest(self, hotkeys,
-                                                         callback, extra))
-            self._requestCondition.notify()
-
-    def requestHotkeysState(self, period, callback, extra = None):
-        """Request a periodic query of the hotkey status."""
-        with self._requestCondition:
-            id = self._nextPeriodicID
-            self._nextPeriodicID += 1
-            request = HotkeysStateRequest(self, id, period, callback, extra)
-            self._periodicRequests.append(request)
-            self._requestCondition.notify()
-            return id
-
-    def unregisterHotkeys(self, callback, extra = None):
-        """Request unregistering the hotkeys."""
-        with self._requestCondition:
-            self._requests.append(UnregisterHotkeysRequest(self,
-                                                           callback, extra))
-            self._requestCondition.notify()
-
-    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()>0:
-                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, attempts = 0):
-        """Try to connect to the flight simulator via XPLRA
-
-        Returns True if the connection has been established, False if it was
-        not due to no longer requested.
-        """
-        while self._connectionRequested:
-            if attempts>=self.NUM_CONNECTATTEMPTS:
-                self._connectionRequested = False
-                if autoReconnection:
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.disconnected())
-                else:
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.connectionFailed())
-                return 0
-
-            try:
-                attempts += 1
-                self._xplane.connect()
-
-                (xplaneVersion, xplmVersion, xplraVersion) = \
-                  self._xplane.getVersions()
-
-                description = "(X-Plane version: %d, XPLM version: %d, XPLRA version: %03d)" % \
-                  (xplaneVersion, xplmVersion, xplraVersion)
-                if not autoReconnection:
-                    fsType = const.SIM_XPLANE10 if xplaneVersion>=10000 else const.SIM_XPLANE9
-
-                    Handler._callSafe(lambda:
-                                      self._connectionListener.connected(fsType,
-                                                                         description))
-                self._connected = True
-                return attempts
-            except Exception, e:
-                print "xplane.Handler._connect: connection failed: " + \
-                      util.utf2unicode(str(e)) + \
-                      " (attempts: %d)" % (attempts,)
-                if attempts<self.NUM_CONNECTATTEMPTS:
-                    time.sleep(self.CONNECT_INTERVAL)
-                self._xplane.disconnect()
-
-    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 "xplane.Handler._disconnect"
-        if self._connected:
-            try:
-                self._xplane.disconnect()
-            except:
-                pass
-
-            self._connected = False
-
-    def _processRequest(self, request, time, attempts):
-        """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 "xplane.Handler._processRequest", request
-
-        needReconnect = False
-        try:
-            self._watchdogClient.set()
-            try:
-                if not request.process(time):
-                    print "xplane.Handler._processRequest: X-Plane returned invalid data too many times, reconnecting"
-                    needReconnect = True
-            except Exception as e:
-                print "xplane.Handler._processRequest: X-Plane connection failed (" + \
-                      util.utf2unicode(str(e)) + \
-                      "), reconnecting (attempts=%d)." % (attempts,)
-                needReconnect = True
-
-            if needReconnect:
-                with self._requestCondition:
-                    self._requests.insert(0, request)
-                self._disconnect()
-                return self._connect(autoReconnection = True, attempts = attempts)
-            else:
-                return 0
-        finally:
-            self._watchdogClient.clear()
-            self._requestCondition.acquire()
-
-    def _processRequests(self):
-        """Process any pending requests.
-
-        Will be called with the request lock held."""
-        attempts = 0
-        while self._connectionRequested and self._periodicRequests:
-            self._periodicRequests.sort()
-            request = self._periodicRequests[0]
-
-            t = time.time()
-
-            if request.nextFire>t:
-                break
-
-            attempts = self._processRequest(request, t, attempts)
-
-        while self._connectionRequested and self._requests:
-            request = self._requests[0]
-            del self._requests[0]
-
-            attempts = self._processRequest(request, None, attempts)
-
-        return self._connectionRequested
-
-#------------------------------------------------------------------------------
-
-class Simulator(object):
-    """The simulator class representing the interface to the flight simulator
-    via XPLRA."""
-    # The basic data that should be queried all the time once we are connected
-    timeData = [ ("sim/time/local_date_days", TYPE_INT),
-                 ("sim/time/zulu_time_sec", TYPE_FLOAT) ]
-
-    normalData = timeData + \
-                 [ ("sim/aircraft/view/acf_tailnum", TYPE_BYTE_ARRAY),
-                   ("sim/aircraft/view/acf_author", TYPE_BYTE_ARRAY),
-                   ("sim/aircraft/view/acf_descrip", TYPE_BYTE_ARRAY),
-                   ("sim/aircraft/view/acf_notes", TYPE_BYTE_ARRAY),
-                   ("sim/aircraft/view/acf_ICAO", TYPE_BYTE_ARRAY),
-                   ("sim/aircraft/view/acf_livery_path", TYPE_BYTE_ARRAY) ]
-
-    flareData1 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
-                   ("sim/flightmodel/position/y_agl", TYPE_FLOAT),
-                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT) ]
-
-    flareStartData = [ ("sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
-                       ("sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
-                       ("sim/weather/visibility_reported_m", TYPE_FLOAT) ]
-
-    flareData2 = [ ("sim/time/zulu_time_sec", TYPE_FLOAT),
-                   ("sim/flightmodel/failures/onground_any", TYPE_INT),
-                   ("sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
-                   ("sim/flightmodel/position/indicated_airspeed2",
-                    TYPE_FLOAT),
-                   ("sim/flightmodel/position/theta", TYPE_FLOAT),
-                   ("sim/flightmodel/position/phi", TYPE_FLOAT),
-                   ("sim/flightmodel/position/psi", TYPE_FLOAT) ]
-
-    TIME_SYNC_INTERVAL = 3.0
-
-    @staticmethod
-    def _getTimestamp(data):
-        """Convert the given data into a timestamp."""
-        year = datetime.date.today().year
-        timestamp = calendar.timegm(time.struct_time([year,
-                                                      1, 1, 0, 0, 0, -1, 1, 0]))
-        timestamp += data[0] * 24 * 3600
-        timestamp += data[1]
-
-        return timestamp
-
-    @staticmethod
-    def _getHotkeyCode(hotkey):
-        """Get the hotkey code for the given hot key."""
-        code = ord(hotkey.key)
-        if hotkey.shift: code |= HOTKEY_MODIFIER_SHIFT
-        if hotkey.ctrl: code |= HOTKEY_MODIFIER_CONTROL
-        return code
-
-    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._fsType = None
-        self._aircraft = None
-
-        self._handler = Handler(self,
-                                connectAttempts = connectAttempts,
-                                connectInterval = connectInterval)
-        self._connectionListener = connectionListener
-        self._handler.start()
-
-        self._syncTime = False
-        self._nextSyncTime = -1
-
-        self._normalRequestID = None
-
-        self._monitoringRequested = False
-        self._monitoring = False
-
-        self._aircraftInfo = None
-        self._aircraftModel = None
-
-        self._flareRequestID = None
-        self._flareRates = []
-        self._flareStart = None
-        self._flareStartFS = None
-
-        self._hotkeyLock = threading.Lock()
-        self._hotkeyCodes = None
-        self._hotkeySetID = 0
-        self._hotkeySetGeneration = 0
-        self._hotkeyOffets = None
-        self._hotkeyRequestID = None
-        self._hotkeyCallback = None
-
-        self._latin1decoder = codecs.getdecoder("iso-8859-1")
-        self._fuelCallback = None
-
-    def connect(self, aircraft):
-        """Initiate a connection to the simulator."""
-        self._aircraft = aircraft
-        self._aircraftInfo = 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."""
-        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
-                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT) ]
-        self._handler.requestRead(data, 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."""
-        data = [ ("sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
-                 ("sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
-                 ("sim/flightmodel/weight/m_total", TYPE_FLOAT) ]
-        self._handler.requestRead(data, self._handleWeights,
-                                  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 handler 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 handler 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."""
-        print "xplra.Simulator.sendMessage:", message
-        self._handler.requestShowMessage(message, duration,
-                                         self._handleMessageSent,
-                                         extra = _disconnect)
-
-    def getFuel(self, callback):
-        """Get the fuel information for the current model.
-
-        The callback will be called with a list of triplets with the following
-        items:
-        - the fuel tank identifier
-        - the current weight of the fuel in the tank (in kgs)
-        - the current total capacity of the tank (in kgs)."""
-        if self._aircraftModel is None:
-            self._fuelCallback = callback
-        else:
-            self._aircraftModel.getFuel(self._handler, 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."""
-        if self._aircraftModel is not None:
-            self._aircraftModel.setFuelLevel(self._handler, levels)
-
-    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 listenHotkeys(self, hotkeys, callback):
-        """Start listening to the given hotkeys.
-
-        callback is function expecting two arguments:
-        - the ID of the hotkey set as returned by this function,
-        - the list of the indexes of the hotkeys that were pressed."""
-        with self._hotkeyLock:
-            assert self._hotkeyCodes is None
-
-            self._hotkeyCodes = \
-              [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
-            self._hotkeySetID += 1
-            self._hotkeySetGeneration = 0
-            self._hotkeyCallback = callback
-
-            self._handler.registerHotkeys(self._hotkeyCodes,
-                                          self._handleHotkeysRegistered,
-                                          (self._hotkeySetID,
-                                           self._hotkeySetGeneration))
-
-            return self._hotkeySetID
-
-    def clearHotkeys(self):
-        """Clear the current hotkey set.
-
-        Note that it is possible, that the callback function set either
-        previously or after calling this function by listenHotkeys() will be
-        called with data from the previous hotkey set.
-
-        Therefore it is recommended to store the hotkey set ID somewhere and
-        check that in the callback function. Right before calling
-        clearHotkeys(), this stored ID should be cleared so that the check
-        fails for sure."""
-        with self._hotkeyLock:
-            if self._hotkeyCodes is not None:
-                self._hotkeyCodes = None
-                self._hotkeySetID += 1
-                self._hotkeyCallback = None
-                self._clearHotkeyRequest()
-
-    def disconnect(self, closingMessage = None, duration = 3):
-        """Disconnect from the simulator."""
-        assert not self._monitoringRequested
-
-        print "xplra.Simulator.disconnect", closingMessage, duration
-
-        self._stopNormal()
-        self.clearHotkeys()
-        if closingMessage is None:
-            self._handler.disconnect()
-        else:
-            self.sendMessage(closingMessage, duration = duration,
-                             _disconnect = True)
-
-    def connected(self, fsType, descriptor):
-        """Called when a connection has been established to the flight
-        simulator of the given type."""
-        self._fsType = fsType
-
-        with self._hotkeyLock:
-            if self._hotkeyCodes is not None:
-                self._hotkeySetGeneration += 1
-
-                self._handler.registerHotkeys(self._hotkeyCodes,
-                                              self._handleHotkeysRegistered,
-                                              (self._hotkeySetID,
-                                               self._hotkeySetGeneration))
-
-        self._connectionListener.connected(fsType, descriptor)
-
-    def connectionFailed(self):
-        """Called when the connection could not be established."""
-        with self._hotkeyLock:
-            self._clearHotkeyRequest()
-        self._connectionListener.connectionFailed()
-
-    def disconnected(self):
-        """Called when a connection to the flight simulator has been broken."""
-        with self._hotkeyLock:
-            self._clearHotkeyRequest()
-        self._connectionListener.disconnected()
-
-    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)
-
-    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 _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.getString(2),
-                                                data.getString(3),
-                                                data.getString(4),
-                                                data.getString(5),
-                                                data.getString(6),
-                                                data.getString(7))
-        if self._fuelCallback is not None:
-            self._aircraftModel.getFuel(self._handler, self._fuelCallback)
-            self._fuelCallback = None
-
-        if self._monitoringRequested and not self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-        elif self._monitoring and not self._monitoringRequested:
-            self._stopNormal()
-            self._startDefaultNormal()
-        elif self._monitoring and self._aircraftModel is not None and \
-             not createdNewModel:
-            aircraftState = self._aircraftModel.getAircraftState(self._aircraft,
-                                                                 timestamp, data)
-
-            self._aircraft.handleState(aircraftState)
-
-    def _setAircraftName(self, timestamp, tailnum, author, description,
-                         notes, icao, liveryPath):
-        """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."""
-        author = self._latin1decoder(author)[0]
-        description = self._latin1decoder(description)[0]
-        notes = self._latin1decoder(notes)[0]
-        liveryPath = self._latin1decoder(liveryPath)[0]
-
-        aircraftInfo = (tailnum, author, description, notes, icao, liveryPath)
-        if aircraftInfo==self._aircraftInfo:
-            return False
-
-        print "xplane.Simulator: new data: %s, %s, %s, %s, %s, %s" % \
-              (tailnum, author, description, notes, icao, liveryPath)
-
-        self._aircraftInfo = aircraftInfo
-        needNew = self._aircraftModel is None
-        needNew = needNew or\
-            not self._aircraftModel.doesHandle(self._aircraft, aircraftInfo)
-        if not needNew:
-            specialModel = AircraftModel.findSpecial(self._aircraft,
-                                                     aircraftInfo)
-            needNew = specialModel is not None and \
-                specialModel is not self._aircraftModel.__class__
-
-        if needNew:
-            self._setAircraftModel(AircraftModel.create(self._aircraft,
-                                                        aircraftInfo))
-
-        self._aircraft.modelChanged(timestamp, description,
-                                    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
-        model.simulator = self
-
-        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._fsType)
-
-        self._normalRequestID = \
-            self._handler.requestPeriodicRead(1.0, data,
-                                              self._handleNormal)
-        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(data)
-
-    def _handleFlare1(self, data, normal):
-        """Handle the first stage of flare monitoring."""
-        #self._aircraft.logger.debug("handleFlare1: " + str(data))
-        if data[1]<=50.0*0.3048:
-            self._flareStart = time.time()
-            self._flareStartFS = data[0]
-            self._handler.clearPeriodic(self._flareRequestID)
-            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]
-            if windDirection<0.0: windDirection += 360.0
-            self._aircraft.flareStarted(data[0], windDirection, data[2],
-                                        self._flareStart, self._flareStartFS)
-
-        self._flareRequestID = \
-          self._handler.requestPeriodicRead(0.1,
-                                            Simulator.flareData2,
-                                            self._handleFlare2)
-
-    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 += 86400.0
-
-            tdRate = min(self._flareRates)
-            tdRateCalculatedByFS = False
-
-            heading = data[6]
-            if heading<0.0: heading += 360.0
-
-            self._aircraft.flareFinished(flareEnd, flareEndFS,
-                                         tdRate, tdRateCalculatedByFS,
-                                         data[3], data[4], data[5], heading)
-        else:
-            self._addFlareRate(data[2])
-
-    def _handleZFW(self, data, callback):
-        """Callback for a ZFW retrieval request."""
-        zfw = data[0] + data[1]
-        callback(zfw)
-
-    def _handleTime(self, data, callback):
-        """Callback for a time retrieval request."""
-        callback(Simulator._getTimestamp(data))
-
-    def _handleWeights(self, data, callback):
-        """Callback for the weights retrieval request."""
-        dow = data[0]
-        payload = data[1]
-        zfw = dow + payload
-        grossWeight = data[2]
-        callback(dow, payload, zfw, grossWeight)
-
-    def _handleMessageSent(self, success, disconnect):
-        """Callback for a message sending request."""
-        #print "xplra.Simulator._handleMessageSent", disconnect
-        if disconnect:
-            self._handler.disconnect()
-
-    def _handleHotkeysRegistered(self, success, (id, generation)):
-        """Handle the result of the hotkeys having been written."""
-        with self._hotkeyLock:
-            if success and id==self._hotkeySetID and \
-            generation==self._hotkeySetGeneration:
-                self._hotkeyRequestID = \
-                  self._handler.requestHotkeysState(0.5,
-                                                    self._handleHotkeys,
-                                                    (id, generation))
-
-    def _handleHotkeys(self, data, (id, generation)):
-        """Handle the hotkeys."""
-        with self._hotkeyLock:
-            if id!=self._hotkeySetID or generation!=self._hotkeySetGeneration:
-                return
-
-            callback = self._hotkeyCallback
-            offsets = self._hotkeyOffets
-
-        hotkeysPressed = []
-        for i in range(0, len(data)):
-            if data[i]:
-                hotkeysPressed.append(i)
-
-        if hotkeysPressed:
-            callback(id, hotkeysPressed)
-
-    def _clearHotkeyRequest(self):
-        """Clear the hotkey request in the handler if there is any."""
-        if self._hotkeyRequestID is not None:
-            self._handler.unregisterHotkeys(self._hotkeysUnregistered)
-            self._handler.clearPeriodic(self._hotkeyRequestID)
-            self._hotkeyRequestID = None
-
-    def _hotkeysUnregistered(self, result, extra):
-        """Called when the hotkeys have been unregistered."""
-        pass
-
-#------------------------------------------------------------------------------
-
-class AircraftModel(object):
-    """Base class for the aircraft models.
-
-    Aircraft models handle the data arriving from X-Plane and turn it into an
-    object describing the aircraft's state."""
-    monitoringData = [ ("paused",
-                        "sim/time/paused", TYPE_INT),
-                       ("latitude",
-                        "sim/flightmodel/position/latitude", TYPE_DOUBLE),
-                       ("longitude",
-                        "sim/flightmodel/position/longitude", TYPE_DOUBLE),
-                       ("replay",
-                        "sim/operation/prefs/replay_mode", TYPE_INT),
-                       ("overspeed",
-                        "sim/flightmodel/failures/over_vne", TYPE_INT),
-                       ("stalled",
-                        "sim/flightmodel/failures/stallwarning", TYPE_INT),
-                       ("onTheGround",
-                        "sim/flightmodel/failures/onground_any", TYPE_INT),
-                       ("emptyWeight",
-                        "sim/aircraft/weight/acf_m_empty", TYPE_FLOAT),
-                       ("payloadWeight",
-                        "sim/flightmodel/weight/m_fixed", TYPE_FLOAT),
-                       ("grossWeight",
-                        "sim/flightmodel/weight/m_total", TYPE_FLOAT),
-                       ("heading",
-                        "sim/flightmodel/position/psi", TYPE_FLOAT),
-                       ("pitch",
-                        "sim/flightmodel/position/theta", TYPE_FLOAT),
-                       ("bank",
-                        "sim/flightmodel/position/phi", TYPE_FLOAT),
-                       ("ias",
-                        "sim/flightmodel/position/indicated_airspeed2",
-                        TYPE_FLOAT),
-                       ("mach",
-                        "sim/flightmodel/misc/machno", TYPE_FLOAT),
-                       ("groundSpeed",
-                        "sim/flightmodel/position/groundspeed", TYPE_FLOAT),
-                       ("vs",
-                        "sim/flightmodel/position/vh_ind_fpm2", TYPE_FLOAT),
-                       ("radioAltitude",
-                        "sim/flightmodel/position/y_agl", TYPE_FLOAT),
-                       ("altitude",
-                        "sim/flightmodel/position/elevation", TYPE_FLOAT),
-                       ("gLoad",
-                        "sim/flightmodel/forces/g_nrml", TYPE_FLOAT),
-                       ("flapsControl",
-                        "sim/flightmodel/controls/flaprqst", TYPE_FLOAT),
-                       ("flapsLeft",
-                        "sim/flightmodel/controls/flaprat", TYPE_FLOAT),
-                       ("flapsRight",
-                        "sim/flightmodel/controls/flap2rat", TYPE_FLOAT),
-                       ("navLights",
-                        "sim/cockpit/electrical/nav_lights_on", TYPE_INT),
-                       ("beaconLights",
-                        "sim/cockpit/electrical/beacon_lights_on", TYPE_INT),
-                       ("strobeLights",
-                        "sim/cockpit/electrical/strobe_lights_on", TYPE_INT),
-                       ("landingLights",
-                        "sim/cockpit/electrical/landing_lights_on", TYPE_INT),
-                       ("pitot",
-                        "sim/cockpit/switches/pitot_heat_on", TYPE_INT),
-                       ("parking",
-                        "sim/flightmodel/controls/parkbrake", TYPE_FLOAT),
-                       ("gearControl",
-                        "sim/cockpit2/controls/gear_handle_down", TYPE_INT),
-                       ("noseGear",
-                        "sim/flightmodel2/gear/deploy_ratio",
-                        (TYPE_FLOAT_ARRAY, 1)),
-                       ("spoilers",
-                        "sim/flightmodel/controls/lsplrdef", TYPE_FLOAT),
-                       ("altimeter",
-                        "sim/cockpit/misc/barometer_setting", TYPE_FLOAT),
-                       ("qnh",
-                        "sim/physics/earth_pressure_p", TYPE_FLOAT),
-                       ("nav1",
-                        "sim/cockpit/radios/nav1_freq_hz", TYPE_INT),
-                       ("nav1_obs",
-                        "sim/cockpit/radios/nav1_obs_degm", TYPE_FLOAT),
-                       ("nav2",
-                        "sim/cockpit/radios/nav2_freq_hz", TYPE_INT),
-                       ("nav2_obs",
-                        "sim/cockpit/radios/nav2_obs_degm", TYPE_FLOAT),
-                       ("adf1",
-                        "sim/cockpit/radios/adf1_freq_hz", TYPE_INT),
-                       ("adf2",
-                        "sim/cockpit/radios/adf2_freq_hz", TYPE_INT),
-                       ("squawk",
-                        "sim/cockpit/radios/transponder_code", TYPE_INT),
-                       ("windSpeed",
-                        "sim/weather/wind_speed_kt[0]", TYPE_FLOAT),
-                       ("windDirection",
-                        "sim/weather/wind_direction_degt[0]", TYPE_FLOAT),
-                       ("visibility",
-                        "sim/weather/visibility_reported_m", TYPE_FLOAT),
-                       ("cog",
-                        "sim/flightmodel/misc/cgz_ref_to_default", TYPE_FLOAT),
-                       ("xpdrC",
-                        "sim/cockpit/radios/transponder_mode", TYPE_INT),
-                       ("apMaster",
-                        "sim/cockpit/autopilot/autopilot_mode", TYPE_INT),
-                       ("apState",
-                        "sim/cockpit/autopilot/autopilot_state", TYPE_INT),
-                       ("apHeading",
-                        "sim/cockpit/autopilot/heading_mag", TYPE_FLOAT),
-                       ("apAltitude",
-                        "sim/cockpit/autopilot/altitude", TYPE_FLOAT),
-                       ("elevatorTrim",
-                        "sim/flightmodel/controls/elv_trim", TYPE_FLOAT),
-                       ("antiIceOn",
-                        "sim/cockpit/switches/anti_ice_on", TYPE_INT),
-                       ("surfaceHeat",
-                        "sim/cockpit/switches/anti_ice_surf_heat", TYPE_INT),
-                       ("propHeat",
-                        "sim/cockpit/switches/anti_ice_prop_heat", TYPE_INT),
-                       ("autopilotOn",
-                        "sim/cockpit2/autopilot/autopilot_on", TYPE_INT),
-                       ("apHeadingMode",
-                        "sim/cockpit2/autopilot/heading_mode", TYPE_INT)]
-
-
-    specialModels = []
-
-    @staticmethod
-    def registerSpecial(clazz):
-        """Register the given class as a special model."""
-        AircraftModel.specialModels.append(clazz)
-
-    @staticmethod
-    def findSpecial(aircraft, aircraftInfo):
-        for specialModel in AircraftModel.specialModels:
-            if specialModel.doesHandle(aircraft, aircraftInfo):
-                return specialModel
-        return None
-
-    @staticmethod
-    def create(aircraft, aircraftInfo):
-        """Create the model for the given aircraft name, and notify the
-        aircraft about it."""
-        specialModel = AircraftModel.findSpecial(aircraft, aircraftInfo)
-        if specialModel is not None:
-            return specialModel()
-        if aircraft.type in _genericModels:
-            return _genericModels[aircraft.type]()
-        else:
-            return GenericModel()
-
-    @staticmethod
-    def _convertFrequency(value):
-        """Convert the given frequency value into a string."""
-        return "%.2f" % (value/100.0,)
-
-    @staticmethod
-    def _convertOBS(value):
-        """Convert the given OBS value into an integer."""
-        while value<0.0:
-            value += 360.0
-        return int(round(value))
-
-    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
-        self._simulator = None
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic"
-
-    @property
-    def simulator(self):
-        """Get the simulator this aircraft model works for."""
-        return self._simulator
-
-    @simulator.setter
-    def simulator(self, simulator):
-        """Get the simulator this aircraft model works for."""
-        self._simulator = simulator
-
-    def doesHandle(self, aircraft, aircraftInfo):
-        """Determine if the model handles the given aircraft name.
-
-        This default implementation returns False."""
-        return False
-
-    def _addDatarefWithIndexMember(self, dest, name, type, attrName = None):
-        """Add the given X-Plane dataref name and type to the given array and a
-        member attribute with the given name."""
-        dest.append((name, type))
-        if attrName is not None:
-            setattr(self, attrName, len(dest)-1)
-
-    def _addDataWithIndexMembers(self, dest, prefix, data):
-        """Add X-Plane dataref 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 X-Plane dataref name
-        - the dataref type
-
-        The latter two items will be appended to dest."""
-        for (name, datarefName, type) in data:
-            self._addDatarefWithIndexMember(dest, datarefName, type,
-                                            prefix + name)
-
-    def addMonitoringData(self, data, fsType):
-        """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()
-
-        lnavOn = data[self._monidx_autopilotOn]!=0 and \
-                 data[self._monidx_apHeadingMode]==2
-
-        state.timestamp = timestamp
-
-        state.latitude = data[self._monidx_latitude]
-        state.longitude = data[self._monidx_longitude]
-        if state.longitude>180.0: state.longitude = 360.0 - state.longitude
-
-        state.paused = data[self._monidx_paused]!=0 or \
-            data[self._monidx_replay]!=0
-        state.trickMode = data[self._monidx_replay]!=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_emptyWeight] + \
-                    data[self._monidx_payloadWeight]
-        state.grossWeight = data[self._monidx_grossWeight]
-
-        state.heading = data[self._monidx_heading]
-
-        state.pitch = -1.0 * data[self._monidx_pitch]
-        state.bank = data[self._monidx_bank]
-
-        state.ias = data[self._monidx_ias]
-        state.mach = data[self._monidx_mach]
-        state.groundSpeed = data[self._monidx_groundSpeed] * _mps2knots
-        state.vs = data[self._monidx_vs]
-
-        state.radioAltitude = data[self._monidx_radioAltitude]/.3048
-        state.altitude = data[self._monidx_altitude]/.3048
-
-        state.gLoad = data[self._monidx_gLoad]
-
-        flapsControl = data[self._monidx_flapsControl]
-        flapsIndex = int(flapsControl * (len(self._flapsNotches)-1))
-        state.flapsSet = 0 if flapsIndex<1 else self._flapsNotches[flapsIndex]
-
-        state.flaps = self._flapsNotches[-1]*data[self._monidx_flapsLeft]
-
-        state.navLightsOn = data[self._monidx_navLights] != 0
-        state.antiCollisionLightsOn = data[self._monidx_beaconLights] != 0
-        state.landingLightsOn = data[self._monidx_landingLights] != 0
-        state.strobeLightsOn = data[self._monidx_strobeLights] != 0
-
-        state.pitotHeatOn = data[self._monidx_pitot]!=0
-
-        state.parking = data[self._monidx_parking]>=0.5
-
-        state.gearControlDown = data[self._monidx_gearControl]!=0
-        state.gearsDown = data[self._monidx_noseGear][0]>0.99
-
-        state.spoilersArmed = None
-
-        state.spoilersExtension = data[self._monidx_spoilers]*100.0
-
-        state.altimeter = data[self._monidx_altimeter]* _hgin2hpa
-        state.altimeterReliable = True
-        state.qnh = data[self._monidx_qnh]/100.0
-
-        state.ils = None
-        state.ils_obs = None
-        state.ils_manual = False
-        state.nav1 = self._convertFrequency(data[self._monidx_nav1])
-        state.nav1_obs = self._convertOBS(data[self._monidx_nav1_obs])
-        state.nav1_manual = True
-        state.nav2 = self._convertFrequency(data[self._monidx_nav2])
-        state.nav2_obs = self._convertOBS(data[self._monidx_nav2_obs])
-        state.nav2_manual = not lnavOn
-        state.adf1 = str(data[self._monidx_adf1])
-        state.adf2 = str(data[self._monidx_adf2])
-
-        state.squawk = "%04d" % (data[self._monidx_squawk],)
-
-        state.windSpeed = data[self._monidx_windSpeed]
-        state.windDirection = data[self._monidx_windDirection]
-        if state.windDirection<0.0: state.windDirection += 360.0
-
-        state.visibility = data[self._monidx_visibility]
-
-        state.cog = data[self._monidx_cog]
-
-        state.xpdrC = data[self._monidx_xpdrC]==2
-        state.autoXPDR = False
-
-        state.apMaster = data[self._monidx_apMaster]==2
-        apState = data[self._monidx_apState]
-        if lnavOn:
-           state.apHeadingHold = None
-           state.apHeading = None
-        else:
-            state.apHeadingHold = (apState&0x00002)!=0
-            state.apHeading = data[self._monidx_apHeading]
-
-        state.apAltitudeHold = (apState&0x04000)!=0
-        state.apAltitude = data[self._monidx_apAltitude]
-
-        state.elevatorTrim = data[self._monidx_elevatorTrim] * 180.0 / math.pi
-
-        state.antiIceOn = data[self._monidx_antiIceOn]!=0 or \
-                          data[self._monidx_surfaceHeat]!=0 or \
-                          data[self._monidx_propHeat]!=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._fuelTankCapacities = [1.0] * len(fuelTanks)
-        self._fuelIndex = None
-        self._numEngines = numEngines
-        self._engineStartIndex = None
-        self._isN1 = isN1
-
-    def doesHandle(self, aircraft, aircraftInfo):
-        """Determine if the model handles the given aircraft name.
-
-        This implementation returns True."""
-        return True
-
-    def addMonitoringData(self, data, fsType):
-        """Add the model-specific monitoring data to the given array."""
-        super(GenericAircraftModel, self).addMonitoringData(data, fsType)
-
-        self._fuelIndex = self._addFuelData(data)
-
-        self._engineStartIndex = len(data)
-        if self._isN1:
-            self._addDatarefWithIndexMember(data,
-                                             "sim/flightmodel/engine/ENGN_N1_",
-                                             (TYPE_FLOAT_ARRAY,
-                                              self._numEngines))
-        else:
-            self._addDatarefWithIndexMember(data,
-                                            "sim/flightmodel/engine/POINT_tacrad",
-                                            (TYPE_FLOAT_ARRAY,
-                                             self._numEngines))
-
-        self._addDatarefWithIndexMember(data,
-                                        "sim/flightmodel/engine/ENGN_propmode",
-                                        (TYPE_INT_ARRAY, self._numEngines))
-
-    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)
-
-        state.fuel = []
-        state.totalFuel = 0.0
-
-        fuelAmounts = data[self._fuelIndex]
-        for i in range(0, len(self._fuelTanks)):
-            amount = fuelAmounts[i]
-            state.fuel.append((self._fuelTanks[i], amount))
-            state.totalFuel += amount
-
-        power = data[self._engineStartIndex]
-
-        state.n1 = power[:] if self._isN1 else None
-        state.rpm = None if self._isN1 else power[:]
-
-        propMode = data[self._engineStartIndex+1]
-        state.reverser = [mode == 3 for mode in propMode]
-
-        return state
-
-    def getFuel(self, handler, callback):
-        """Get the fuel information for this model.
-
-        See Simulator.getFuel for more information. This
-        implementation simply queries the fuel tanks given to the
-        constructor."""
-        data = []
-        self._addFuelData(data)
-        data.append( ("sim/aircraft/weight/acf_m_fuel_tot", TYPE_FLOAT) )
-        data.append( ("sim/aircraft/overflow/acf_tank_rat",
-                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks)) ) )
-
-        handler.requestRead(data, self._handleFuelRetrieved,
-                            extra = callback)
-
-    def setFuelLevel(self, handler, levels):
-        """Set the fuel level.
-
-        See the description of Simulator.setFuelLevel. This
-        implementation simply sets the fuel tanks as given."""
-        data = []
-        for (tank, level) in levels:
-            try:
-                index = self._fuelTanks.index(tank)
-                data.append( ("sim/flightmodel/weight/m_fuel",
-                              (TYPE_FLOAT_ARRAY, 1, index),
-                              [level * self._fuelTankCapacities[index]]) )
-            except:
-                print "xplane.Simulator.setFuelLevel: invalid tank constant: %d" % \
-                  (tank,)
-
-        handler.requestWrite(data, self._handleFuelWritten)
-
-    def _addFuelData(self, data):
-        """Add the fuel offsets to the given data array.
-
-        Returns the index of the first fuel tank's data."""
-        fuelStartIndex = len(data)
-        data.append( ("sim/flightmodel/weight/m_fuel",
-                      (TYPE_FLOAT_ARRAY, len(self._fuelTanks) ) ) )
-
-        return fuelStartIndex
-
-    def _convertFuelData(self, data, index = 0, addCapacities = False):
-        """Convert the given data into a fuel info list.
-
-        The list consists of two or three-tuples of the following
-        items:
-        - the fuel tank ID,
-        - the amount of the fuel in kg,
-        - if addCapacities is True, the total capacity of the tank."""
-        fuelWeight = data[index] / 256.0
-        index += 1
-
-        result = []
-        totalFuel = 0
-        for fuelTank in self._fuelTanks:
-            capacity = data[index+1] * fuelWeight * const.LBSTOKG
-            if capacity>=1.0:
-                amount = data[index] * capacity / 128.0 / 65536.0
-
-                result.append( (fuelTank, amount, capacity) if addCapacities
-                               else (fuelTank, amount))
-                totalFuel += amount
-            index += 2
-
-        return (result, totalFuel)
-
-    def _handleFuelRetrieved(self, data, callback):
-        """Callback for a fuel retrieval request."""
-        result = []
-        totalCapacity = data[1]
-        for index in range(0, len(self._fuelTanks)):
-            amount = data[0][index]
-            capacity = data[2][index] * totalCapacity
-            self._fuelTankCapacities[index] = capacity
-            result.append( (self._fuelTanks[index], amount, capacity) )
-
-        callback(result)
-
-    def _handleFuelWritten(self, success, extra):
-        """Callback for a fuel setting request."""
-        pass
-
-#------------------------------------------------------------------------------
-
-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 "X-Plane/Generic"
-
-#------------------------------------------------------------------------------
-
-class B737Model(GenericAircraftModel):
-    """Generic model for the Boeing 737 Classing and NG aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(B737Model, self). \
-            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
-                     fuelTanks = B737Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Boeing 737"
-
-#------------------------------------------------------------------------------
-
-class B767Model(GenericAircraftModel):
-    """Generic model for the Boeing 767 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(B767Model, self). \
-            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
-                     fuelTanks = B767Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Boeing 767"
-
-#------------------------------------------------------------------------------
-
-class DH8DModel(GenericAircraftModel):
-    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(DH8DModel, self). \
-            __init__(flapsNotches = [0, 5, 10, 15, 35],
-                     fuelTanks = DH8DModel.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Bombardier Dash 8-Q400"
-
-#------------------------------------------------------------------------------
-
-class FJSDH8DModel(DH8DModel):
-    """Model handler for the FlyJSim Dash 8-Q400."""
-    @staticmethod
-    def doesHandle(aircraft, (tailnum, author, description, notes,
-                              icao, liveryPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type==const.AIRCRAFT_DH8D and \
-          description.find("Dash 8 Q400")!=-1 and \
-          ((author in ["2012", "2013"] and tailnum=="N62890") or \
-           author.find("Jack Skieczius")!=-1)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/FlyJSim Bombardier Dash 8-Q400"
-
-    def addMonitoringData(self, data, fsType):
-        """Add the model-specific monitoring data to the given array."""
-        super(FJSDH8DModel, self).addMonitoringData(data, fsType)
-
-        self._speedBrakeIndex = len(data)
-        self._addDatarefWithIndexMember(data,
-                                        "sim/flightmodel2/wing/speedbrake1_deg",
-                                        (TYPE_FLOAT_ARRAY, 2))
-        self._addDatarefWithIndexMember(data,
-                                        "sim/flightmodel2/wing/speedbrake2_deg",
-                                        (TYPE_FLOAT_ARRAY, 2))
-
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then invert the pitot heat state."""
-        state = super(FJSDH8DModel, self).getAircraftState(aircraft,
-                                                           timestamp,
-                                                           data)
-        state.antiCollisionLightsOn = \
-          state.antiCollisionLightsOn or state.strobeLightsOn
-        state.cog = (state.cog / 0.0254 + 21.504) / 94.512
-
-        # It seems that N1 does not always go down to 0 properly
-        # (maybe due to winds?)
-        state.n1 = [0 if n1<2.0 else n1 for n1 in state.n1]
-
-        state.spoilersExtension = \
-            sum(data[self._speedBrakeIndex] + data[self._speedBrakeIndex+1])/4
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class CRJ2Model(GenericAircraftModel):
-    """Generic model for the Bombardier CRJ-200 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(CRJ2Model, self). \
-            __init__(flapsNotches = [0, 8, 20, 30, 45],
-                     fuelTanks = CRJ2Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Bombardier CRJ-200"
-
-#------------------------------------------------------------------------------
-
-class F70Model(GenericAircraftModel):
-    """Generic model for the Fokker F70 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(F70Model, self). \
-            __init__(flapsNotches = [0, 8, 15, 25, 42],
-                     fuelTanks = F70Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Fokker 70"
-
-#------------------------------------------------------------------------------
-
-class DC3Model(GenericAircraftModel):
-    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT]
-    # fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-    #              const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-
-    def __init__(self):
-        """Construct the model."""
-        super(DC3Model, self). \
-            __init__(flapsNotches = [0, 15, 30, 45],
-                     fuelTanks = DC3Model.fuelTanks,
-                     numEngines = 2, isN1 = False)
-        self._leftLevel = 0.0
-        self._rightLevel = 0.0
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Lisunov Li-2 (DC-3)"
-
-#------------------------------------------------------------------------------
-
-class T134Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
-                 const.FUELTANK_LEFT_AUX,
-                 const.FUELTANK_CENTRE,
-                 const.FUELTANK_RIGHT_AUX,
-                 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
-
-    def __init__(self):
-        """Construct the model."""
-        super(T134Model, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelTanks = T134Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Tupolev Tu-134"
-
-#------------------------------------------------------------------------------
-
-class T154Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    fuelTanks = [const.FUELTANK_LEFT_AUX, const.FUELTANK_LEFT,
-                 const.FUELTANK_CENTRE, const.FUELTANK_CENTRE2,
-                 const.FUELTANK_RIGHT, const.FUELTANK_RIGHT_AUX]
-
-    def __init__(self):
-        """Construct the model."""
-        super(T154Model, self). \
-            __init__(flapsNotches = [0, 15, 28, 45],
-                     fuelTanks = T154Model.fuelTanks,
-                     numEngines = 3)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/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."""
-    fuelTanks = [const.FUELTANK_LEFT, const.FUELTANK_RIGHT]
-
-    def __init__(self):
-        """Construct the model."""
-        super(YK40Model, self). \
-            __init__(flapsNotches = [0, 20, 35],
-                     fuelTanks = YK40Model.fuelTanks,
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "X-Plane/Generic Yakovlev Yak-40"
-
-#------------------------------------------------------------------------------
-
-_genericModels = { const.AIRCRAFT_B736  : B737Model,
-                   const.AIRCRAFT_B737  : B737Model,
-                   const.AIRCRAFT_B738  : B737Model,
-                   const.AIRCRAFT_B738C : 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  : CRJ2Model,
-                   const.AIRCRAFT_F70   : F70Model,
-                   const.AIRCRAFT_DC3   : DC3Model,
-                   const.AIRCRAFT_T134  : T134Model,
-                   const.AIRCRAFT_T154  : T154Model,
-                   const.AIRCRAFT_YK40  : YK40Model }
-
-#------------------------------------------------------------------------------
-
-AircraftModel.registerSpecial(FJSDH8DModel)
-
-#------------------------------------------------------------------------------
-
-# if __name__ == "__main__":
-#     class ConnectionListener:
-#         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 Config:
-#         def __init__(self):
-#             self.onlineACARS = False
-#             self.realIASSmoothingLength = 2
-#             self.realVSSmoothingLength = 2
-#             self.enableSounds = False
-#             self.usingFS2Crew = False
-
-#         def isMessageTypeFS(self, type):
-#             return True
-
-
-
-#     class GUI:
-#         def __init__(self):
-#             self.config = Config()
-#             self.entranceExam = False
-#             self.zfw = 30000.0
-
-#         def resetFlightStatus(self):
-#             pass
-
-#         def setRating(self, value):
-#             pass
-
-#         def insertFlightLogLine(self, index, ts, text, isFault):
-#             pass
-
-#         def setStage(self, stage):
-#             pass
-
-
-#     from i18n import setLanguage
-
-#     setLanguage("/home/vi/munka/repules/mlx", "en")
-
-#     from logger import Logger
-#     from flight import Flight
-#     from acft import DH8D
-
-#     gui = GUI()
-
-#     logger = Logger(gui)
-
-#     flight = Flight(logger, gui)
-#     acft = DH8D(flight)
-
-#     Watchdog()
-
-#     connectionListener = ConnectionListener()
-#     simulator = Simulator(connectionListener, connectAttempts = 3)
-
-#     simulator.connect(acft)
-
-#     time.sleep(2)
-
-#     simulator.startMonitoring()
-
-#     simulator.sendMessage("[MLX] Flight stage: Taxi", duration = 3)
-
-#     time.sleep(4)
-
-#     simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116, R117, R210, R211, R212, R212A, R220, R221, R222, R223, R224, R225, R226, R227, R270, R271, R272, R274, R275, R276, R277, R278, R278A, R279", duration = 20)
-#     #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115, R116", duration = 20)
-#     #simulator.sendMessage("[MLX] Free gates: 1, 2, 3, 4, 5, 6, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 107, 108, 109, R113, R114, R115", duration = 20)
-
-#     time.sleep(30)
-
-#     simulator.sendMessage("[MLX] Hello", duration = 3)
-
-#     time.sleep(10)
