# -*- 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
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"Petrovszki", u"Gábor", "test"),
(u"Zsebényi-Loksa", u"Gergely", "test"),
(u"Kurják", u"Ákos", "test"),
(u"Nagy", u"Dániel", "test"),
(u"Radó", u"Iván", "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._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.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)
accelGroup = gtk.AccelGroup()
window.add_accel_group(accelGroup)
mainVBox = gtk.VBox()
window.add(mainVBox)
self._preferences = Preferences(self)
self._checklistEditor = ChecklistEditor(self)
self._approachCalloutsEditor = ApproachCalloutsEditor(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._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()
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 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 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()
singleton.raiseCallback = self.raiseCallback
gtk.main()
singleton.raiseCallback = None
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._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")
gobject.idle_add(self._disconnected)
def _disconnected(self):
"""Called when we have disconnected from the simulator unexpectedly."""
self._statusbar.updateConnection(self._connecting, self._connected)
dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
message_format = xstr("conn_broken"),
parent = self._mainWindow)
dialog.set_title(WINDOW_TITLE_BASE)
dialog.format_secondary_markup(xstr("conn_broken_sec"))
dialog.add_button(xstr("button_cancel"), 0)
dialog.add_button(xstr("button_reconnect"), 1)
dialog.set_default_response(1)
result = dialog.run()
dialog.hide()
if result == 1:
self.beginBusy(xstr("connect_busy"))
self._reconnecting = True
self._simulator.reconnect()
else:
self.reset()
def enableFlightInfo(self):
"""Enable the flight info tab."""
self._flightInfo.enable()
def 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._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 addFlightLogLine(self, timeStr, line, isFault = False):
"""Write the given message line to the log."""
gobject.idle_add(self._writeLog,
formatFlightLogLine(timeStr, line),
self._logView, isFault)
def updateFlightLogLine(self, index, timeStr, line):
"""Update the line with the given index."""
gobject.idle_add(self._updateFlightLogLine, index,
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:
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 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 endBusy(self):
"""End a period of background processing."""
self._mainWindow.get_window().set_cursor(None)
self._weightHelp.set_sensitive(True)
self._wizard.set_sensitive(True)
self._statusbar.updateBusyState(None)
def initializeWeightHelp(self):
"""Initialize the weight help tab."""
self._weightHelp.reset()
self._weightHelp.enable()
def getFleetAsync(self, callback = None, force = None):
"""Get the fleet asynchronously."""
gobject.idle_add(self.getFleet, callback, force)
def getFleet(self, callback = None, force = False):
"""Get the fleet.
If force is False, and we already have a fleet retrieved,
that one will be used."""
if self._fleet is None or force:
self._fleetCallback = callback
self.beginBusy(xstr("fleet_busy"))
self.webHandler.getFleet(self._fleetResultCallback)
else:
callback(self._fleet)
def _fleetResultCallback(self, returned, result):
"""Called when the fleet has been queried."""
gobject.idle_add(self._handleFleetResult, returned, result)
def _handleFleetResult(self, returned, result):
"""Handle the fleet result."""
self.endBusy()
if returned:
self._fleet = result.fleet
else:
self._fleet = None
dialog = gtk.MessageDialog(parent = self.mainWindow,
type = MESSAGETYPE_ERROR,
message_format = xstr("fleet_failed"))
dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
dialog.set_title(WINDOW_TITLE_BASE)
dialog.run()
dialog.hide()
callback = self._fleetCallback
self._fleetCallback = None
if callback is not None:
callback(self._fleet)
self._fleetGateStatus.handleFleet(self._fleet)
def updatePlane(self, tailNumber, status,
gateNumber = None, callback = None):
"""Update the status of the given plane."""
self.beginBusy(xstr("fleet_update_busy"))
self._updatePlaneCallback = callback
self._updatePlaneTailNumber = tailNumber
self._updatePlaneStatus = status
self._updatePlaneGateNumber = gateNumber
self.webHandler.updatePlane(self._updatePlaneResultCallback,
tailNumber, status, gateNumber)
def _updatePlaneResultCallback(self, returned, result):
"""Called when the status of a plane has been updated."""
gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
def _handleUpdatePlaneResult(self, returned, result):
"""Handle the plane update result."""
self.endBusy()
if returned:
success = result.success
if success:
if self._fleet is not None:
self._fleet.updatePlane(self._updatePlaneTailNumber,
self._updatePlaneStatus,
self._updatePlaneGateNumber)
self._fleetGateStatus.handleFleet(self._fleet)
else:
dialog = gtk.MessageDialog(parent = self.mainWindow,
type = MESSAGETYPE_ERROR,
message_format = xstr("fleet_update_failed"))
dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
dialog.set_title(WINDOW_TITLE_BASE)
dialog.run()
dialog.hide()
success = None
callback = self._updatePlaneCallback
self._updatePlaneCallback = None
if callback is not None:
callback(success)
def _writeStdIO(self):
"""Perform the real writing."""
with self._stdioLock:
text = self._stdioText
self._stdioText = ""
if not text: return
lines = text.splitlines()
if text[-1]=="\n":
text = ""
else:
text = lines[-1]
lines = lines[:-1]
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):
"""Connect to the simulator for the first time."""
self._logger.reset()
self._flight = flight.Flight(self._logger, self)
self._flight.flareTimeFromFS = self.config.flareTimeFromFS
self._flight.aircraftType = aircraftType
self._flight.aircraft = acft.Aircraft.create(self._flight)
self._flight.aircraft._checkers.append(self)
if self._simulator is None:
self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
fs.setupMessageSending(self.config, self._simulator)
self._setupTimeSync()
self._flight.simulator = self._simulator
self.beginBusy(xstr("connect_busy"))
self._statusbar.updateConnection(self._connecting, self._connected)
self._connecting = True
self._simulator.connect(self._flight.aircraft)
def startMonitoring(self):
"""Start monitoring."""
if not self._monitoring:
self.simulator.startMonitoring()
self._monitoring = True
def stopMonitoring(self):
"""Stop monitoring."""
if self._monitoring:
self.simulator.stopMonitoring()
self._monitoring = False
def _buildMenuBar(self, accelGroup):
"""Build the main menu bar."""
menuBar = gtk.MenuBar()
fileMenuItem = gtk.MenuItem(xstr("menu_file"))
fileMenu = gtk.Menu()
fileMenuItem.set_submenu(fileMenu)
menuBar.append(fileMenuItem)
loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
loadPIREPMenuItem.set_use_stock(True)
loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
loadPIREPMenuItem.add_accelerator("activate", accelGroup,
ord(xstr("menu_file_loadPIREP_key")),
CONTROL_MASK, ACCEL_VISIBLE)
loadPIREPMenuItem.connect("activate", self._loadPIREP)
fileMenu.append(loadPIREPMenuItem)
fileMenu.append(gtk.SeparatorMenuItem())
quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
quitMenuItem.set_use_stock(True)
quitMenuItem.set_label(xstr("menu_file_quit"))
quitMenuItem.add_accelerator("activate", accelGroup,
ord(xstr("menu_file_quit_key")),
CONTROL_MASK, ACCEL_VISIBLE)
quitMenuItem.connect("activate", self._quit)
fileMenu.append(quitMenuItem)
toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
toolsMenu = gtk.Menu()
toolsMenuItem.set_submenu(toolsMenu)
menuBar.append(toolsMenuItem)
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)
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 _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 _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("" + xstr("loadPIREP_send_flightno") + "")
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("" + xstr("loadPIREP_send_date") + "")
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("" + xstr("loadPIREP_send_from") + "")
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("" + xstr("loadPIREP_send_to") + "")
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("" + xstr("loadPIREP_send_rating") + "")
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('NO GO')
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 _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)