# The flight handling "wizard" from mlx.gui.common import * import mlx.const as const import mlx.fs as fs from mlx.logger import Logger from mlx.flight import Flight from mlx.acft import Aircraft #----------------------------------------------------------------------------- class Page(gtk.Alignment): """A page in the flight wizard.""" def __init__(self, wizard, title, help): """Construct the page.""" super(Page, self).__init__(xalign = 0.0, yalign = 0.0, xscale = 1.0, yscale = 1.0) self.set_padding(padding_top = 4, padding_bottom = 4, padding_left = 12, padding_right = 12) frame = gtk.Frame() self.add(frame) style = self.get_style() if pygobject else self.rc_get_style() self._vbox = gtk.VBox() self._vbox.set_homogeneous(False) frame.add(self._vbox) eventBox = gtk.EventBox() eventBox.modify_bg(0, style.bg[3]) alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0) label = gtk.Label(title) label.modify_fg(0, style.fg[3]) label.modify_font(pango.FontDescription("bold 24")) alignment.set_padding(padding_top = 4, padding_bottom = 4, padding_left = 6, padding_right = 0) alignment.add(label) eventBox.add(alignment) self._vbox.pack_start(eventBox, False, False, 0) table = gtk.Table(3, 1) table.set_homogeneous(True) alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0, xscale = 1.0, yscale = 1.0) alignment.set_padding(padding_top = 16, padding_bottom = 16, padding_left = 16, padding_right = 16) alignment.add(table) self._vbox.pack_start(alignment, True, True, 0) alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0, yscale = 0.0) alignment.set_padding(padding_top = 0, padding_bottom = 16, padding_left = 0, padding_right = 0) label = gtk.Label(help) label.set_justify(gtk.Justification.CENTER if pygobject else gtk.JUSTIFY_CENTER) alignment.add(label) table.attach(alignment, 0, 1, 0, 1) self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5, xscale = 1.0, yscale = 1.0) table.attach(self._mainAlignment, 0, 1, 1, 3) buttonAlignment = gtk.Alignment(xalign = 1.0, xscale=0.0, yscale = 0.0) buttonAlignment.set_padding(padding_top = 4, padding_bottom = 10, padding_left = 16, padding_right = 16) self._buttonBox = gtk.HButtonBox() self._defaultButton = None buttonAlignment.add(self._buttonBox) self._vbox.pack_start(buttonAlignment, False, False, 0) self._wizard = wizard def setMainWidget(self, widget): """Set the given widget as the main one.""" self._mainAlignment.add(widget) def addButton(self, label, default = False): """Add a button with the given label. Return the button object created.""" button = gtk.Button(label) self._buttonBox.add(button) button.set_use_underline(True) if default: button.set_can_default(True) self._defaultButton = button return button def activate(self): """Called when this page becomes active. This default implementation does nothing.""" pass def grabDefault(self): """If the page has a default button, make it the default one.""" if self._defaultButton is not None: self._defaultButton.grab_default() #----------------------------------------------------------------------------- class LoginPage(Page): """The login page.""" def __init__(self, wizard): """Construct the login page.""" help = "Enter your MAVA pilot's ID and password to\n" \ "log in to the MAVA website and download\n" \ "your booked flights." super(LoginPage, self).__init__(wizard, "Login", help) alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5, xscale = 0.0, yscale = 0.0) table = gtk.Table(2, 3) table.set_row_spacings(4) table.set_col_spacings(32) alignment.add(table) self.setMainWidget(alignment) labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0) label = gtk.Label("Pilot _ID:") label.set_use_underline(True) labelAlignment.add(label) table.attach(labelAlignment, 0, 1, 0, 1) self._pilotID = gtk.Entry() self._pilotID.connect("changed", self._setLoginButton) self._pilotID.set_tooltip_text("Enter your MAVA pilot's ID. This " "usually starts with a " "'P' followed by 3 digits.") table.attach(self._pilotID, 1, 2, 0, 1) label.set_mnemonic_widget(self._pilotID) labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0) label = gtk.Label("_Password:") label.set_use_underline(True) labelAlignment.add(label) table.attach(labelAlignment, 0, 1, 1, 2) self._password = gtk.Entry() self._password.set_visibility(False) self._password.connect("changed", self._setLoginButton) self._password.set_tooltip_text("Enter the password for your pilot's ID") table.attach(self._password, 1, 2, 1, 2) label.set_mnemonic_widget(self._password) self._rememberButton = gtk.CheckButton("_Remember password") self._rememberButton.set_use_underline(True) self._rememberButton.set_tooltip_text("If checked, your password will " "be stored, so that you should " "not have to enter it every time. " "Note, however, that the password " "is stored as text, and anybody " "who can access your files will " "be able to read it.") table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8) self._loginButton = self.addButton("_Login", default = True) self._loginButton.set_sensitive(False) self._loginButton.connect("clicked", self._loginClicked) self._loginButton.set_tooltip_text("Click to log in.") config = self._wizard.gui.config self._pilotID.set_text(config.pilotID) self._password.set_text(config.password) self._rememberButton.set_active(config.rememberPassword) def _setLoginButton(self, entry): """Set the login button's sensitivity. The button is sensitive only if both the pilot ID and the password fields contain values.""" self._loginButton.set_sensitive(self._pilotID.get_text()!="" and self._password.get_text()!="") def _loginClicked(self, button): """Called when the login button was clicked.""" self._wizard.gui.beginBusy("Logging in...") self._wizard.gui.webHandler.login(self._loginResultCallback, self._pilotID.get_text(), self._password.get_text()) def _loginResultCallback(self, returned, result): """The login result callback, called in the web handler's thread.""" gobject.idle_add(self._handleLoginResult, returned, result) def _handleLoginResult(self, returned, result): """Handle the login result.""" self._wizard.gui.endBusy() if returned: if result.loggedIn: config = self._wizard.gui.config config.pilotID = self._pilotID.get_text() rememberPassword = self._rememberButton.get_active() config.password = self._password.get_text() if rememberPassword \ else "" config.rememberPassword = rememberPassword config.save() self._wizard._loginResult = result self._wizard.nextPage() else: dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR, buttons = BUTTONSTYPE_OK, message_format = "Invalid pilot's ID or password.") dialog.format_secondary_markup("Check the ID and try to reenter" " the password.") dialog.run() dialog.hide() else: dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR, buttons = BUTTONSTYPE_OK, message_format = "Failed to connect to the MAVA website.") dialog.format_secondary_markup("Try again in a few minutes.") dialog.run() dialog.hide() #----------------------------------------------------------------------------- class FlightSelectionPage(Page): """The page to select the flight.""" def __init__(self, wizard): """Construct the flight selection page.""" super(FlightSelectionPage, self).__init__(wizard, "Flight selection", "Select the flight you want " "to perform.") self._listStore = gtk.ListStore(str, str, str, str) self._flightList = gtk.TreeView(self._listStore) column = gtk.TreeViewColumn("Flight no.", gtk.CellRendererText(), text = 1) column.set_expand(True) self._flightList.append_column(column) column = gtk.TreeViewColumn("Departure time [UTC]", gtk.CellRendererText(), text = 0) column.set_expand(True) self._flightList.append_column(column) column = gtk.TreeViewColumn("From", gtk.CellRendererText(), text = 2) column.set_expand(True) self._flightList.append_column(column) column = gtk.TreeViewColumn("To", gtk.CellRendererText(), text = 3) column.set_expand(True) self._flightList.append_column(column) flightSelection = self._flightList.get_selection() flightSelection.connect("changed", self._selectionChanged) scrolledWindow = gtk.ScrolledWindow() scrolledWindow.add(self._flightList) scrolledWindow.set_size_request(400, -1) scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject else gtk.POLICY_AUTOMATIC, gtk.PolicyType.ALWAYS if pygobject else gtk.POLICY_ALWAYS) alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0) alignment.add(scrolledWindow) self.setMainWidget(alignment) self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True) self._button.set_use_stock(True) self._button.set_sensitive(False) self._button.connect("clicked", self._forwardClicked) self._activated = False def activate(self): """Fill the flight list.""" if not self._activated: for flight in self._wizard.loginResult.flights: self._listStore.append([str(flight.departureTime), flight.callsign, flight.departureICAO, flight.arrivalICAO]) self._activated = True def _selectionChanged(self, selection): """Called when the selection is changed.""" self._button.set_sensitive(selection.count_selected_rows()==1) def _forwardClicked(self, button): """Called when the forward button was clicked.""" selection = self._flightList.get_selection() (listStore, iter) = selection.get_selected() path = listStore.get_path(iter) [index] = path.get_indices() if pygobject else path flight = self._wizard.loginResult.flights[index] self._wizard._bookedFlight = flight self._updateDepartureGate() def _updateDepartureGate(self): """Update the departure gate for the booked flight.""" flight = self._wizard._bookedFlight if flight.departureICAO=="LHBP": self._wizard._getFleet(self._fleetRetrieved) else: self._wizard.jumpPage(2) def _fleetRetrieved(self, fleet): """Called when the fleet has been retrieved.""" if fleet is None: self._wizard.jumpPage(2) else: plane = fleet[self._wizard._bookedFlight.tailNumber] if plane is None: self._wizard.jumpPage(2) if plane.gateNumber is not None and \ not fleet.isGateConflicting(plane): self._wizard._departureGate = plane.gateNumber self._wizard.jumpPage(2) else: self._wizard.nextPage() #----------------------------------------------------------------------------- class GateSelectionPage(Page): """Page to select a free gate at LHBP. This page should be displayed only if we have fleet information!.""" def __init__(self, wizard): """Construct the gate selection page.""" help = "The airplane's gate position is invalid.\n\n" \ "Select the gate from which you\n" \ "would like to begin the flight." super(GateSelectionPage, self).__init__(wizard, "LHBP gate selection", help) self._listStore = gtk.ListStore(str) self._gateList = gtk.TreeView(self._listStore) column = gtk.TreeViewColumn(None, gtk.CellRendererText(), text = 0) column.set_expand(True) self._gateList.append_column(column) self._gateList.set_headers_visible(False) gateSelection = self._gateList.get_selection() gateSelection.connect("changed", self._selectionChanged) scrolledWindow = gtk.ScrolledWindow() scrolledWindow.add(self._gateList) scrolledWindow.set_size_request(50, -1) scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject else gtk.POLICY_AUTOMATIC, gtk.PolicyType.ALWAYS if pygobject else gtk.POLICY_ALWAYS) alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0) alignment.add(scrolledWindow) self.setMainWidget(alignment) self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True) self._button.set_use_stock(True) self._button.set_sensitive(False) self._button.connect("clicked", self._forwardClicked) def activate(self): """Fill the gate list.""" self._listStore.clear() occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers() for gateNumber in const.lhbpGateNumbers: if gateNumber not in occupiedGateNumbers: self._listStore.append([gateNumber]) def _selectionChanged(self, selection): """Called when the selection is changed.""" self._button.set_sensitive(selection.count_selected_rows()==1) def _forwardClicked(self, button): """Called when the forward button is clicked.""" 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(type = MESSAGETYPE_ERROR, buttons = BUTTONSTYPE_OK, message_format = "Gate conflict detected again") dialog.format_secondary_markup("Try to select a different gate.") dialog.run() dialog.hide() self._wizard._getFleet(self._fleetRetrieved) def _fleetRetrieved(self, fleet): """Called when the fleet has been retrieved.""" if fleet is None: self._wizard.nextPage() else: self.activate() #----------------------------------------------------------------------------- class ConnectPage(Page): """Page which displays the departure airport and gate (if at LHBP).""" def __init__(self, wizard): """Construct the connect page.""" help = "The flight begins at the airport given below.\n" \ "Park your aircraft there, at the gate below, if given.\n\n" \ "Then press the Connect button to connect to the simulator." super(ConnectPage, self).__init__(wizard, "Connect to the simulator", help) alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5, xscale = 0.0, yscale = 0.0) table = gtk.Table(2, 2) table.set_row_spacings(4) table.set_col_spacings(16) table.set_homogeneous(True) alignment.add(table) self.setMainWidget(alignment) labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0) label = gtk.Label("ICAO code:") labelAlignment.add(label) table.attach(labelAlignment, 0, 1, 0, 1) labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0) self._departureICAO = gtk.Label() self._departureICAO.set_width_chars(5) self._departureICAO.set_alignment(0.0, 0.5) labelAlignment.add(self._departureICAO) table.attach(labelAlignment, 1, 2, 0, 1) labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0) label = gtk.Label("Gate:") label.set_use_underline(True) labelAlignment.add(label) table.attach(labelAlignment, 0, 1, 1, 2) labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0) self._departureGate = gtk.Label() self._departureGate.set_width_chars(5) self._departureGate.set_alignment(0.0, 0.5) labelAlignment.add(self._departureGate) table.attach(labelAlignment, 1, 2, 1, 2) self._button = self.addButton("_Connect", default = True) self._button.set_use_underline(True) self._button.connect("clicked", self._connectClicked) def activate(self): """Setup the deprature information.""" icao = self._wizard._bookedFlight.departureICAO self._departureICAO.set_markup("" + icao + "") gate = self._wizard._departureGate if gate!="-": gate = "" + gate + "" self._departureGate.set_markup(gate) def _connectClicked(self, button): """Called when the Connect button is pressed.""" self._wizard._connectSimulator() #----------------------------------------------------------------------------- class Wizard(gtk.VBox): """The flight wizard.""" def __init__(self, gui): """Construct the wizard.""" super(Wizard, self).__init__() self.gui = gui self._pages = [] self._currentPage = None self._pages.append(LoginPage(self)) self._pages.append(FlightSelectionPage(self)) self._pages.append(GateSelectionPage(self)) self._pages.append(ConnectPage(self)) maxWidth = 0 maxHeight = 0 for page in self._pages: page.show_all() pageSizeRequest = page.size_request() width = pageSizeRequest.width if pygobject else pageSizeRequest[0] height = pageSizeRequest.height if pygobject else pageSizeRequest[1] maxWidth = max(maxWidth, width) maxHeight = max(maxHeight, height) maxWidth += 16 maxHeight += 32 self.set_size_request(maxWidth, maxHeight) self._fleet = None self._fleetCallback = None self._updatePlaneCallback = None self._loginResult = None self._bookedFlight = None self._departureGate = "-" self._logger = Logger(output = gui) self._flight = None self._simulator = None self.setCurrentPage(0) @property def loginResult(self): """Get the login result.""" return self._loginResult def setCurrentPage(self, index): """Set the current page to the one with the given index.""" assert index < len(self._pages) if self._currentPage is not None: self.remove(self._pages[self._currentPage]) self._currentPage = index self.add(self._pages[index]) self._pages[index].activate() self.show_all() def nextPage(self): """Go to the next page.""" self.jumpPage(1) def jumpPage(self, count): """Go to the page which is 'count' pages after the current one.""" self.setCurrentPage(self._currentPage + count) self.grabDefault() def grabDefault(self): """Make the default button of the current page the default.""" self._pages[self._currentPage].grabDefault() def _getFleet(self, callback, force = False): """Get the fleet, if needed. callback is function that will be called, when the feet is retrieved, or the retrieval fails. It should have a single argument that will receive the fleet object on success, None otherwise. """ if self._fleet is not None and not force: callback(self._fleet) self.gui.beginBusy("Retrieving fleet...") self._fleetCallback = callback self.gui.webHandler.getFleet(self._fleetResultCallback) def _fleetResultCallback(self, returned, result): """Called when the fleet has been queried.""" gobject.idle_add(self._handleFleetResult, returned, result) def _handleFleetResult(self, returned, result): """Handle the fleet result.""" self.gui.endBusy() if returned: self._fleet = result.fleet else: self._fleet = None dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR, buttons = BUTTONSTYPE_OK, message_format = "Failed to retrieve the information on " "the fleet.") dialog.run() dialog.hide() self._fleetCallback(self._fleet) def _updatePlane(self, callback, tailNumber, status, gateNumber = None): """Update the given plane's gate information.""" self.gui.beginBusy("Updating plane status...") self._updatePlaneCallback = callback self.gui.webHandler.updatePlane(self._updatePlaneResultCallback, tailNumber, status, gateNumber) def _updatePlaneResultCallback(self, returned, result): """Callback for the plane updating operation.""" gobject.idle_add(self._handleUpdatePlaneResult, returned, result) def _handleUpdatePlaneResult(self, returned, result): """Handle the result of a plane update operation.""" self.gui.endBusy() if returned: success = result.success else: success = None dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR, buttons = BUTTONSTYPE_OK, message_format = "Failed to update the statuis of " "the airplane.") dialog.run() dialog.hide() self._updatePlaneCallback(success) def _connectSimulator(self): """Connect to the simulator.""" self._logger.reset() self._flight = Flight(self.gui._logger, self.gui) self._flight.aircraftType = self._bookedFlight.aircraftType aircraft = self._flight.aircraft = Aircraft.create(self._flight) self._flight.aircraft._checkers.append(self.gui) self._flight.cruiseAltitude = -1 self._flight.zfw = -1 if self._simulator is None: self._simulator = fs.createSimulator(const.SIM_MSFS9, self.gui) self._flight.simulator = self._simulator self._simulator.connect(aircraft) #self._simulator.startMonitoring() #-----------------------------------------------------------------------------