# The main file for the GUI
from statusicon import StatusIcon
from statusbar import Statusbar
from info import FlightInfo
from update import Updater
from mlx.gui.common import *
from mlx.gui.flight import Wizard
from mlx.gui.monitor import MonitorWindow
import mlx.const as const
import mlx.fs as fs
import mlx.flight as flight
import mlx.logger as logger
import mlx.acft as acft
import mlx.web as web
import time
import threading
import sys
#------------------------------------------------------------------------------
class GUI(fs.ConnectionListener):
"""The main GUI class."""
@staticmethod
def _formatFlightLogLine(timeStr, line):
"""Format the given line for flight logging."""
if timeStr is not None:
line = timeStr + ": " + line
return line + "\n"
def __init__(self, programDirectory, config):
"""Construct the GUI."""
gobject.threads_init()
self._programDirectory = programDirectory
self.config = config
self._connecting = False
self._reconnecting = False
self._connected = False
self._logger = logger.Logger(self)
self._flight = None
self._simulator = None
self._monitoring = False
self._stdioLock = threading.Lock()
self._stdioText = ""
self.webHandler = web.Handler()
self.webHandler.start()
self.toRestart = False
def build(self, iconDirectory):
"""Build the GUI."""
window = gtk.Window()
window.set_title("MAVA Logger X " + const.VERSION)
window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
window.connect("delete-event",
lambda a, b: self.hideMainWindow())
window.connect("window-state-event", self._handleMainWindowState)
accelGroup = gtk.AccelGroup()
window.add_accel_group(accelGroup)
mainVBox = gtk.VBox()
window.add(mainVBox)
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("Fligh_t")
label.set_use_underline(True)
label.set_tooltip_text("Flight wizard")
self._notebook.append_page(self._wizard, label)
self._flightInfo = FlightInfo(self)
label = gtk.Label("Flight _info")
label.set_use_underline(True)
label.set_tooltip_text("Flight information")
self._notebook.append_page(self._flightInfo, label)
self._flightInfo.disable()
(logWidget, self._logView) = self._buildLogWidget()
label = gtk.Label("_Log")
label.set_use_underline(True)
label.set_tooltip_text("The log of your flight that will be sent to the MAVA website")
self._notebook.append_page(logWidget, label)
(self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
self._debugLogWidget.show_all()
mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
self._statusbar = Statusbar()
mainVBox.pack_start(self._statusbar, False, False, 0)
self._notebook.connect("switch-page", self._notebookPageSwitch)
self._monitorWindow = MonitorWindow(self, iconDirectory)
self._monitorWindow.add_accel_group(accelGroup)
self._monitorWindowX = None
self._monitorWindowY = None
self._selfToggling = False
window.show_all()
self._wizard.grabDefault()
self._mainWindow = window
self._statusIcon = StatusIcon(iconDirectory, self)
self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
else gdk.WATCH)
@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 bookedFlight(self):
"""Get the booked flight selected, if any."""
return self._wizard.bookedFlight
@property
def cargoWeight(self):
"""Get the cargo weight."""
return self._wizard.cargoWeight
@property
def zfw(self):
"""Get Zero-Fuel Weight calculated for the current flight."""
return self._wizard.zfw
@property
def filedCruiseAltitude(self):
"""Get cruise altitude filed for the current flight."""
return self._wizard.filedCruiseAltitude
@property
def cruiseAltitude(self):
"""Get cruise altitude set for the current flight."""
return self._wizard.cruiseAltitude
@property
def route(self):
"""Get the flight route."""
return self._wizard.route
@property
def departureMETAR(self):
"""Get the METAR of the deprature airport."""
return self._wizard.departureMETAR
@property
def arrivalMETAR(self):
"""Get the METAR of the deprature airport."""
return self._wizard.arrivalMETAR
@property
def departureRunway(self):
"""Get the name of the departure runway."""
return self._wizard.departureRunway
@property
def sid(self):
"""Get the SID."""
return self._wizard.sid
@property
def v1(self):
"""Get the V1 speed calculated for the flight."""
return self._wizard.v1
@property
def vr(self):
"""Get the Vr speed calculated for the flight."""
return self._wizard.vr
@property
def v2(self):
"""Get the V2 speed calculated for the flight."""
return self._wizard.v2
@property
def arrivalRunway(self):
"""Get the arrival runway."""
return self._wizard.arrivalRunway
@property
def star(self):
"""Get the STAR."""
return self._wizard.star
@property
def transition(self):
"""Get the transition."""
return self._wizard.transition
@property
def approachType(self):
"""Get the approach type."""
return self._wizard.approachType
@property
def vref(self):
"""Get the Vref speed calculated for the flight."""
return self._wizard.vref
@property
def flightType(self):
"""Get the flight type."""
return self._wizard.flightType
@property
def online(self):
"""Get whether the flight was online or not."""
return self._wizard.online
@property
def comments(self):
"""Get the comments."""
return self._flightInfo.comments
@property
def flightDefects(self):
"""Get the flight defects."""
return self._flightInfo.flightDefects
@property
def delayCodes(self):
"""Get the delay codes."""
return self._flightInfo.delayCodes
def run(self):
"""Run the GUI."""
if self.config.autoUpdate:
self._updater = Updater(self,
self._programDirectory,
self.config.updateURL,
self._mainWindow)
self._updater.start()
gtk.main()
self._disconnect()
def connected(self, fsType, descriptor):
"""Called when we have connected to the simulator."""
self._connected = True
self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
gobject.idle_add(self._handleConnected, fsType, descriptor)
def _handleConnected(self, fsType, descriptor):
"""Called when the connection to the simulator has succeeded."""
self._statusbar.updateConnection(self._connecting, self._connected)
self.endBusy()
if not self._reconnecting:
self._wizard.connected(fsType, descriptor)
self._reconnecting = False
def connectionFailed(self):
"""Called when the connection failed."""
self._logger.untimedMessage("Connection to the simulator failed")
gobject.idle_add(self._connectionFailed)
def _connectionFailed(self):
"""Called when the connection failed."""
self.endBusy()
self._statusbar.updateConnection(self._connecting, self._connected)
dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
message_format =
"Cannot connect to the simulator.",
parent = self._mainWindow)
dialog.format_secondary_markup("Rectify the situation, and press Try again "
"to try the connection again, "
"or Cancel to cancel the flight.")
dialog.add_button("_Cancel", 0)
dialog.add_button("_Try again", 1)
dialog.set_default_response(1)
result = dialog.run()
dialog.hide()
if result == 1:
self.beginBusy("Connecting to the simulator.")
self._simulator.reconnect()
else:
self.reset()
def disconnected(self):
"""Called when we have disconnected from the simulator."""
self._connected = False
self._logger.untimedMessage("Disconnected from the simulator")
gobject.idle_add(self._disconnected)
def _disconnected(self):
"""Called when we have disconnected from the simulator unexpectedly."""
self._statusbar.updateConnection(self._connecting, self._connected)
dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
message_format =
"The connection to the simulator failed unexpectedly.",
parent = self._mainWindow)
dialog.format_secondary_markup("If the simulator has crashed, restart it "
"and restore your flight as much as possible "
"to the state it was in before the crash.\n"
"Then press Reconnect to reconnect.\n\n"
"If you want to cancel the flight, press Cancel.")
dialog.add_button("_Cancel", 0)
dialog.add_button("_Reconnect", 1)
dialog.set_default_response(1)
result = dialog.run()
dialog.hide()
if result == 1:
self.beginBusy("Connecting to the simulator.")
self._reconnecting = True
self._simulator.reconnect()
else:
self.reset()
def enableFlightInfo(self):
"""Enable the flight info tab."""
self._flightInfo.enable()
def reset(self):
"""Reset the GUI."""
self._disconnect()
self._flightInfo.reset()
self._flightInfo.disable()
self.resetFlightStatus()
self._wizard.reset()
self._notebook.set_current_page(0)
self._logView.get_buffer().set_text("")
def _disconnect(self):
"""Disconnect from the simulator if connected."""
self.stopMonitoring()
if self._connected:
self._flight.simulator.disconnect()
self._connected = False
self._connecting = False
self._reconnecting = False
self._statusbar.updateConnection(False, False)
def addFlightLogLine(self, timeStr, line):
"""Write the given message line to the log."""
gobject.idle_add(self._writeLog,
GUI._formatFlightLogLine(timeStr, line),
self._logView)
def updateFlightLogLine(self, index, timeStr, line):
"""Update the line with the given index."""
gobject.idle_add(self._updateFlightLogLine, index,
GUI._formatFlightLogLine(timeStr, line))
def _updateFlightLogLine(self, index, line):
"""Replace the contents of the given line in the log."""
buffer = self._logView.get_buffer()
startIter = buffer.get_iter_at_line(index)
endIter = buffer.get_iter_at_line(index + 1)
buffer.delete(startIter, endIter)
buffer.insert(startIter, line)
self._logView.scroll_mark_onscreen(buffer.get_insert())
def check(self, flight, aircraft, logger, oldState, state):
"""Update the data."""
gobject.idle_add(self._monitorWindow.setData, state)
gobject.idle_add(self._statusbar.updateTime, state.timestamp)
def resetFlightStatus(self):
"""Reset the status of the flight."""
self._statusbar.resetFlightStatus()
self._statusbar.updateTime()
self._statusIcon.resetFlightStatus()
def setStage(self, stage):
"""Set the stage of the flight."""
gobject.idle_add(self._setStage, stage)
def _setStage(self, stage):
"""Set the stage of the flight."""
self._statusbar.setStage(stage)
self._statusIcon.setStage(stage)
self._wizard.setStage(stage)
if stage==const.STAGE_END:
self._disconnect()
def setRating(self, rating):
"""Set the rating of the flight."""
gobject.idle_add(self._setRating, rating)
def _setRating(self, rating):
"""Set the rating of the flight."""
self._statusbar.setRating(rating)
self._statusIcon.setRating(rating)
def setNoGo(self, reason):
"""Set the rating of the flight to No-Go with the given reason."""
gobject.idle_add(self._setNoGo, reason)
def _setNoGo(self, reason):
"""Set the rating of the flight."""
self._statusbar.setNoGo(reason)
self._statusIcon.setNoGo(reason)
def _handleMainWindowState(self, window, event):
"""Hande a change in the state of the window"""
iconified = gdk.WindowState.ICONIFIED if pygobject \
else gdk.WINDOW_STATE_ICONIFIED
if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
self.hideMainWindow(savePosition = False)
def hideMainWindow(self, savePosition = True):
"""Hide the main window and save its position."""
if savePosition:
(self._mainWindowX, self._mainWindowY) = \
self._mainWindow.get_window().get_root_origin()
else:
self._mainWindowX = self._mainWindowY = None
self._mainWindow.hide()
self._statusIcon.mainWindowHidden()
return True
def showMainWindow(self):
"""Show the main window at its former position."""
if self._mainWindowX is not None and self._mainWindowY is not None:
self._mainWindow.move(self._mainWindowX, self._mainWindowY)
self._mainWindow.show()
self._mainWindow.deiconify()
self._statusIcon.mainWindowShown()
def toggleMainWindow(self):
"""Toggle the main window."""
if self._mainWindow.get_visible():
self.hideMainWindow()
else:
self.showMainWindow()
def hideMonitorWindow(self, savePosition = True):
"""Hide the monitor window."""
if savePosition:
(self._monitorWindowX, self._monitorWindowY) = \
self._monitorWindow.get_window().get_root_origin()
else:
self._monitorWindowX = self._monitorWindowY = None
self._monitorWindow.hide()
self._statusIcon.monitorWindowHidden()
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._mainWindow.get_window().set_cursor(self._busyCursor)
self._statusbar.updateBusyState(message)
def endBusy(self):
"""End a period of background processing."""
self._mainWindow.get_window().set_cursor(None)
self._wizard.set_sensitive(True)
self._statusbar.updateBusyState(None)
def _writeStdIO(self):
"""Perform the real writing."""
with self._stdioLock:
text = self._stdioText
self._stdioText = ""
if not text: return
lines = text.splitlines()
if text[-1]=="\n":
text = ""
else:
text = lines[-1]
lines = lines[:-1]
for line in lines:
#print >> sys.__stdout__, line
self._writeLog(line + "\n", self._debugLogView)
if text:
#print >> sys.__stdout__, text,
self._writeLog(text, self._debugLogView)
def connectSimulator(self, aircraftType):
"""Connect to the simulator for the first time."""
self._logger.reset()
self._flight = flight.Flight(self._logger, self)
self._flight.aircraftType = aircraftType
self._flight.aircraft = acft.Aircraft.create(self._flight)
self._flight.aircraft._checkers.append(self)
if self._simulator is None:
self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
self._flight.simulator = self._simulator
self.beginBusy("Connecting to the simulator...")
self._statusbar.updateConnection(self._connecting, self._connected)
self._connecting = True
self._simulator.connect(self._flight.aircraft)
def startMonitoring(self):
"""Start monitoring."""
if not self._monitoring:
self.simulator.startMonitoring()
self._monitoring = True
def stopMonitoring(self):
"""Stop monitoring."""
if self._monitoring:
self.simulator.stopMonitoring()
self._monitoring = False
def _buildMenuBar(self, accelGroup):
"""Build the main menu bar."""
menuBar = gtk.MenuBar()
fileMenuItem = gtk.MenuItem("File")
fileMenu = gtk.Menu()
fileMenuItem.set_submenu(fileMenu)
menuBar.append(fileMenuItem)
quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
quitMenuItem.set_use_stock(True)
quitMenuItem.add_accelerator("activate", accelGroup,
ord("q"), CONTROL_MASK,
ACCEL_VISIBLE)
quitMenuItem.connect("activate", self._quit)
fileMenu.append(quitMenuItem)
viewMenuItem = gtk.MenuItem("View")
viewMenu = gtk.Menu()
viewMenuItem.set_submenu(viewMenu)
menuBar.append(viewMenuItem)
self._showMonitorMenuItem = gtk.CheckMenuItem()
self._showMonitorMenuItem.set_label("Show _monitor window")
self._showMonitorMenuItem.set_use_underline(True)
self._showMonitorMenuItem.set_active(False)
self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
ord("m"), CONTROL_MASK,
ACCEL_VISIBLE)
self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
viewMenu.append(self._showMonitorMenuItem)
showDebugMenuItem = gtk.CheckMenuItem()
showDebugMenuItem.set_label("Show _debug log")
showDebugMenuItem.set_use_underline(True)
showDebugMenuItem.set_active(False)
showDebugMenuItem.add_accelerator("activate", accelGroup,
ord("d"), CONTROL_MASK,
ACCEL_VISIBLE)
showDebugMenuItem.connect("toggled", self._toggleDebugLog)
viewMenu.append(showDebugMenuItem)
return menuBar
def _toggleDebugLog(self, menuItem):
"""Toggle the debug log."""
if menuItem.get_active():
label = gtk.Label("_Debug log")
label.set_use_underline(True)
label.set_tooltip_text("Log with debugging information.")
self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
self._notebook.set_current_page(self._debugLogPage)
else:
self._notebook.remove_page(self._debugLogPage)
def _buildLogWidget(self):
"""Build the widget for the log."""
alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
alignment.set_padding(padding_top = 8, padding_bottom = 8,
padding_left = 16, padding_right = 16)
logScroller = gtk.ScrolledWindow()
# FIXME: these should be constants in common
logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
else gtk.POLICY_AUTOMATIC,
gtk.PolicyType.AUTOMATIC if pygobject
else gtk.POLICY_AUTOMATIC)
logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
else gtk.SHADOW_IN)
logView = gtk.TextView()
logView.set_editable(False)
logScroller.add(logView)
logBox = gtk.VBox()
logBox.pack_start(logScroller, True, True, 0)
logBox.set_size_request(-1, 200)
alignment.add(logBox)
return (alignment, logView)
def _writeLog(self, msg, logView):
"""Write the given message to the log."""
buffer = logView.get_buffer()
buffer.insert(buffer.get_end_iter(), msg)
logView.scroll_mark_onscreen(buffer.get_insert())
def _quit(self, what = None, force = False):
"""Quit from the application."""
if force:
result=RESPONSETYPE_YES
else:
dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
buttons = BUTTONSTYPE_YES_NO,
message_format =
"Are you sure to quit the logger?")
result = dialog.run()
dialog.hide()
if result==RESPONSETYPE_YES:
self._statusIcon.destroy()
return gtk.main_quit()
def _notebookPageSwitch(self, notebook, page, page_num):
"""Called when the current page of the notebook has changed."""
if page_num==0:
gobject.idle_add(self._wizard.grabDefault)
else:
self._mainWindow.set_default(None)