source: src/mlx/gui/gui.py@ 215:bff7327b2da0

Last change on this file since 215:bff7327b2da0 was 215:bff7327b2da0, checked in by István Váradi <ivaradi@…>, 12 years ago

Added support for flying without logging in

File size: 44.4 KB
RevLine 
[28]1# The main file for the GUI
2
[29]3from statusicon import StatusIcon
[32]4from statusbar import Statusbar
[90]5from info import FlightInfo
[36]6from update import Updater
[29]7from mlx.gui.common import *
[42]8from mlx.gui.flight import Wizard
[77]9from mlx.gui.monitor import MonitorWindow
[117]10from mlx.gui.weighthelp import WeightHelp
[118]11from mlx.gui.gates import FleetGateStatus
[123]12from mlx.gui.prefs import Preferences
[172]13from mlx.gui.checklist import ChecklistEditor
[28]14
15import mlx.const as const
16import mlx.fs as fs
17import mlx.flight as flight
18import mlx.logger as logger
19import mlx.acft as acft
[41]20import mlx.web as web
[182]21import mlx.singleton as singleton
[107]22from mlx.i18n import xstr
[151]23from mlx.pirep import PIREP
[28]24
25import time
[38]26import threading
27import sys
[203]28import datetime
[28]29
[77]30#------------------------------------------------------------------------------
31
[28]32class GUI(fs.ConnectionListener):
33 """The main GUI class."""
[96]34 @staticmethod
35 def _formatFlightLogLine(timeStr, line):
36 """Format the given line for flight logging."""
37 if timeStr is not None:
38 line = timeStr + ": " + line
39 return line + "\n"
40
[36]41 def __init__(self, programDirectory, config):
[28]42 """Construct the GUI."""
43 gobject.threads_init()
44
[36]45 self._programDirectory = programDirectory
[42]46 self.config = config
[28]47 self._connecting = False
[59]48 self._reconnecting = False
[28]49 self._connected = False
[96]50 self._logger = logger.Logger(self)
[28]51 self._flight = None
52 self._simulator = None
[59]53 self._monitoring = False
[130]54
[119]55 self._fleet = None
[130]56
[119]57 self._fleetCallback = None
[38]58
[130]59 self._updatePlaneCallback = None
60 self._updatePlaneTailNumber = None
61 self._updatePlaneStatus = None
62 self._updatePlaneGateNumber = None
63
[38]64 self._stdioLock = threading.Lock()
65 self._stdioText = ""
[203]66 self._stdioStartingLine = True
[28]67
[151]68 self._sendPIREPCallback = None
69
[41]70 self.webHandler = web.Handler()
71 self.webHandler.start()
72
[38]73 self.toRestart = False
74
[29]75 def build(self, iconDirectory):
[28]76 """Build the GUI."""
[29]77
[123]78 self._mainWindow = window = gtk.Window()
[105]79 window.set_title(WINDOW_TITLE_BASE)
[36]80 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
[202]81 window.set_resizable(False)
[36]82 window.connect("delete-event",
83 lambda a, b: self.hideMainWindow())
84 window.connect("window-state-event", self._handleMainWindowState)
[93]85 accelGroup = gtk.AccelGroup()
86 window.add_accel_group(accelGroup)
[28]87
88 mainVBox = gtk.VBox()
[36]89 window.add(mainVBox)
[28]90
[123]91 self._preferences = Preferences(self)
[172]92 self._checklistEditor = ChecklistEditor(self)
[123]93
[93]94 menuBar = self._buildMenuBar(accelGroup)
95 mainVBox.pack_start(menuBar, False, False, 0)
96
[92]97 self._notebook = gtk.Notebook()
[93]98 mainVBox.pack_start(self._notebook, True, True, 4)
[142]99
[46]100 self._wizard = Wizard(self)
[107]101 label = gtk.Label(xstr("tab_flight"))
[46]102 label.set_use_underline(True)
[108]103 label.set_tooltip_text(xstr("tab_flight_tooltip"))
[92]104 self._notebook.append_page(self._wizard, label)
[42]105
[90]106 self._flightInfo = FlightInfo(self)
[107]107 label = gtk.Label(xstr("tab_flight_info"))
[90]108 label.set_use_underline(True)
[108]109 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
[92]110 self._notebook.append_page(self._flightInfo, label)
[93]111 self._flightInfo.disable()
[90]112
[117]113 self._weightHelp = WeightHelp(self)
114 label = gtk.Label(xstr("tab_weight_help"))
115 label.set_use_underline(True)
116 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
117 self._notebook.append_page(self._weightHelp, label)
118
[93]119 (logWidget, self._logView) = self._buildLogWidget()
[107]120 label = gtk.Label(xstr("tab_log"))
[46]121 label.set_use_underline(True)
[108]122 label.set_tooltip_text(xstr("tab_log_tooltip"))
[93]123 self._notebook.append_page(logWidget, label)
124
[118]125 self._fleetGateStatus = FleetGateStatus(self)
126 label = gtk.Label(xstr("tab_gates"))
127 label.set_use_underline(True)
128 label.set_tooltip_text(xstr("tab_gates_tooltip"))
129 self._notebook.append_page(self._fleetGateStatus, label)
130
[93]131 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
132 self._debugLogWidget.show_all()
[32]133
134 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
135
136 self._statusbar = Statusbar()
137 mainVBox.pack_start(self._statusbar, False, False, 0)
138
[92]139 self._notebook.connect("switch-page", self._notebookPageSwitch)
[46]140
[77]141 self._monitorWindow = MonitorWindow(self, iconDirectory)
[93]142 self._monitorWindow.add_accel_group(accelGroup)
[77]143 self._monitorWindowX = None
144 self._monitorWindowY = None
[93]145 self._selfToggling = False
[77]146
[46]147 window.show_all()
148 self._wizard.grabDefault()
[117]149 self._weightHelp.reset()
150 self._weightHelp.disable()
[28]151
[29]152 self._statusIcon = StatusIcon(iconDirectory, self)
[28]153
[49]154 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
155 else gdk.WATCH)
156
[151]157 self._loadPIREPDialog = None
158 self._lastLoadedPIREP = None
159
[168]160 self._hotkeySetID = None
[178]161 self._pilotHotkeyIndex = None
162 self._checklistHotkeyIndex = None
[168]163
[59]164 @property
[105]165 def mainWindow(self):
166 """Get the main window of the GUI."""
167 return self._mainWindow
168
169 @property
[97]170 def logger(self):
171 """Get the logger used by us."""
172 return self._logger
173
174 @property
[59]175 def simulator(self):
176 """Get the simulator used by us."""
177 return self._simulator
178
[71]179 @property
180 def flight(self):
181 """Get the flight being performed."""
182 return self._flight
[84]183
184 @property
[184]185 def entranceExam(self):
186 """Get whether an entrance exam is about to be taken."""
187 return self._wizard.entranceExam
[215]188
189 @property
190 def loggedIn(self):
191 """Indicate if the user has logged in properly."""
192 return self._wizard.loggedIn
[184]193
194 @property
[139]195 def loginResult(self):
196 """Get the result of the login."""
197 return self._wizard.loginResult
198
199 @property
[97]200 def bookedFlight(self):
201 """Get the booked flight selected, if any."""
202 return self._wizard.bookedFlight
203
204 @property
205 def cargoWeight(self):
206 """Get the cargo weight."""
207 return self._wizard.cargoWeight
208
209 @property
[84]210 def zfw(self):
211 """Get Zero-Fuel Weight calculated for the current flight."""
212 return self._wizard.zfw
213
214 @property
[97]215 def filedCruiseAltitude(self):
216 """Get cruise altitude filed for the current flight."""
217 return self._wizard.filedCruiseAltitude
218
219 @property
[84]220 def cruiseAltitude(self):
[97]221 """Get cruise altitude set for the current flight."""
[84]222 return self._wizard.cruiseAltitude
[97]223
224 @property
225 def route(self):
226 """Get the flight route."""
227 return self._wizard.route
228
229 @property
230 def departureMETAR(self):
231 """Get the METAR of the deprature airport."""
232 return self._wizard.departureMETAR
[84]233
234 @property
[97]235 def arrivalMETAR(self):
236 """Get the METAR of the deprature airport."""
237 return self._wizard.arrivalMETAR
238
239 @property
240 def departureRunway(self):
241 """Get the name of the departure runway."""
242 return self._wizard.departureRunway
243
244 @property
245 def sid(self):
246 """Get the SID."""
247 return self._wizard.sid
248
249 @property
[84]250 def v1(self):
251 """Get the V1 speed calculated for the flight."""
252 return self._wizard.v1
253
254 @property
255 def vr(self):
256 """Get the Vr speed calculated for the flight."""
257 return self._wizard.vr
258
259 @property
260 def v2(self):
261 """Get the V2 speed calculated for the flight."""
262 return self._wizard.v2
[71]263
[86]264 @property
[97]265 def arrivalRunway(self):
266 """Get the arrival runway."""
267 return self._wizard.arrivalRunway
268
269 @property
270 def star(self):
271 """Get the STAR."""
272 return self._wizard.star
273
274 @property
275 def transition(self):
276 """Get the transition."""
277 return self._wizard.transition
278
279 @property
280 def approachType(self):
281 """Get the approach type."""
282 return self._wizard.approachType
283
284 @property
[86]285 def vref(self):
286 """Get the Vref speed calculated for the flight."""
287 return self._wizard.vref
288
[97]289 @property
290 def flightType(self):
291 """Get the flight type."""
292 return self._wizard.flightType
293
294 @property
295 def online(self):
296 """Get whether the flight was online or not."""
297 return self._wizard.online
298
299 @property
300 def comments(self):
301 """Get the comments."""
302 return self._flightInfo.comments
303
304 @property
305 def flightDefects(self):
306 """Get the flight defects."""
307 return self._flightInfo.flightDefects
308
[99]309 @property
310 def delayCodes(self):
311 """Get the delay codes."""
312 return self._flightInfo.delayCodes
313
[28]314 def run(self):
315 """Run the GUI."""
[42]316 if self.config.autoUpdate:
[38]317 self._updater = Updater(self,
318 self._programDirectory,
[42]319 self.config.updateURL,
[36]320 self._mainWindow)
321 self._updater.start()
[182]322
323 singleton.raiseCallback = self.raiseCallback
[28]324 gtk.main()
[182]325 singleton.raiseCallback = None
[36]326
[91]327 self._disconnect()
[28]328
329 def connected(self, fsType, descriptor):
330 """Called when we have connected to the simulator."""
331 self._connected = True
332 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
[133]333 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
334 "Welcome to MAVA Logger X " + const.VERSION)
[59]335 gobject.idle_add(self._handleConnected, fsType, descriptor)
336
337 def _handleConnected(self, fsType, descriptor):
338 """Called when the connection to the simulator has succeeded."""
339 self._statusbar.updateConnection(self._connecting, self._connected)
340 self.endBusy()
341 if not self._reconnecting:
342 self._wizard.connected(fsType, descriptor)
343 self._reconnecting = False
[168]344 self._listenHotkeys()
[59]345
346 def connectionFailed(self):
347 """Called when the connection failed."""
348 self._logger.untimedMessage("Connection to the simulator failed")
349 gobject.idle_add(self._connectionFailed)
350
351 def _connectionFailed(self):
352 """Called when the connection failed."""
353 self.endBusy()
354 self._statusbar.updateConnection(self._connecting, self._connected)
355
[105]356 dialog = gtk.MessageDialog(parent = self._mainWindow,
357 type = MESSAGETYPE_ERROR,
[108]358 message_format = xstr("conn_failed"))
359
[105]360 dialog.set_title(WINDOW_TITLE_BASE)
[108]361 dialog.format_secondary_markup(xstr("conn_failed_sec"))
[59]362
[108]363 dialog.add_button(xstr("button_cancel"), 0)
364 dialog.add_button(xstr("button_tryagain"), 1)
[59]365 dialog.set_default_response(1)
366
367 result = dialog.run()
368 dialog.hide()
369 if result == 1:
[107]370 self.beginBusy(xstr("connect_busy"))
[59]371 self._simulator.reconnect()
372 else:
[91]373 self.reset()
[28]374
375 def disconnected(self):
376 """Called when we have disconnected from the simulator."""
377 self._connected = False
378 self._logger.untimedMessage("Disconnected from the simulator")
[59]379
380 gobject.idle_add(self._disconnected)
381
382 def _disconnected(self):
383 """Called when we have disconnected from the simulator unexpectedly."""
384 self._statusbar.updateConnection(self._connecting, self._connected)
385
386 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
[108]387 message_format = xstr("conn_broken"),
[59]388 parent = self._mainWindow)
[105]389 dialog.set_title(WINDOW_TITLE_BASE)
[108]390 dialog.format_secondary_markup(xstr("conn_broken_sec"))
[59]391
[108]392 dialog.add_button(xstr("button_cancel"), 0)
393 dialog.add_button(xstr("button_reconnect"), 1)
[59]394 dialog.set_default_response(1)
395
396 result = dialog.run()
397 dialog.hide()
398 if result == 1:
[108]399 self.beginBusy(xstr("connect_busy"))
[59]400 self._reconnecting = True
401 self._simulator.reconnect()
402 else:
[91]403 self.reset()
404
[93]405 def enableFlightInfo(self):
406 """Enable the flight info tab."""
407 self._flightInfo.enable()
408
[208]409 def cancelFlight(self):
410 """Cancel the current file, if the user confirms it."""
411 dialog = gtk.MessageDialog(parent = self._mainWindow,
412 type = MESSAGETYPE_QUESTION,
413 message_format = xstr("cancelFlight_question"))
414
415 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
416 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
417
418 dialog.set_title(WINDOW_TITLE_BASE)
419 result = dialog.run()
420 dialog.hide()
421
422 if result==RESPONSETYPE_YES:
423 self.reset()
424
[91]425 def reset(self):
426 """Reset the GUI."""
427 self._disconnect()
[92]428
[91]429 self._flightInfo.reset()
[93]430 self._flightInfo.disable()
[91]431 self.resetFlightStatus()
[28]432
[117]433 self._weightHelp.reset()
434 self._weightHelp.disable()
[92]435 self._notebook.set_current_page(0)
436
437 self._logView.get_buffer().set_text("")
438
[215]439 if self.loggedIn:
440 self._wizard.reloadFlights(self._handleReloadResult)
441 else:
442 self._wizard.reset(None)
[208]443
444 def _handleReloadResult(self, returned, result):
445 """Handle the result of the reloading of the flights."""
446 self._wizard.reset(result if returned and result.loggedIn else None)
447
[152]448 def _disconnect(self, closingMessage = None, duration = 3):
[91]449 """Disconnect from the simulator if connected."""
[92]450 self.stopMonitoring()
[168]451 self._clearHotkeys()
[92]452
[91]453 if self._connected:
[152]454 if closingMessage is None:
455 self._flight.simulator.disconnect()
456 else:
457 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
458 closingMessage, duration,
459 disconnect = True)
[91]460 self._connected = False
461
462 self._connecting = False
463 self._reconnecting = False
464 self._statusbar.updateConnection(False, False)
[128]465 self._weightHelp.disable()
[134]466
467 return True
[91]468
[96]469 def addFlightLogLine(self, timeStr, line):
470 """Write the given message line to the log."""
471 gobject.idle_add(self._writeLog,
472 GUI._formatFlightLogLine(timeStr, line),
473 self._logView)
474
475 def updateFlightLogLine(self, index, timeStr, line):
476 """Update the line with the given index."""
477 gobject.idle_add(self._updateFlightLogLine, index,
478 GUI._formatFlightLogLine(timeStr, line))
479
480 def _updateFlightLogLine(self, index, line):
481 """Replace the contents of the given line in the log."""
482 buffer = self._logView.get_buffer()
483 startIter = buffer.get_iter_at_line(index)
484 endIter = buffer.get_iter_at_line(index + 1)
485 buffer.delete(startIter, endIter)
486 buffer.insert(startIter, line)
487 self._logView.scroll_mark_onscreen(buffer.get_insert())
488
[28]489 def check(self, flight, aircraft, logger, oldState, state):
490 """Update the data."""
[77]491 gobject.idle_add(self._monitorWindow.setData, state)
[80]492 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
[28]493
[31]494 def resetFlightStatus(self):
495 """Reset the status of the flight."""
[32]496 self._statusbar.resetFlightStatus()
[80]497 self._statusbar.updateTime()
[31]498 self._statusIcon.resetFlightStatus()
499
500 def setStage(self, stage):
501 """Set the stage of the flight."""
502 gobject.idle_add(self._setStage, stage)
503
504 def _setStage(self, stage):
505 """Set the stage of the flight."""
[32]506 self._statusbar.setStage(stage)
[31]507 self._statusIcon.setStage(stage)
[84]508 self._wizard.setStage(stage)
[88]509 if stage==const.STAGE_END:
[152]510 self._disconnect(closingMessage =
511 "Flight plan closed. Welcome to %s" % \
512 (self.bookedFlight.arrivalICAO,),
513 duration = 5)
[31]514
515 def setRating(self, rating):
516 """Set the rating of the flight."""
517 gobject.idle_add(self._setRating, rating)
518
519 def _setRating(self, rating):
520 """Set the rating of the flight."""
[32]521 self._statusbar.setRating(rating)
[31]522 self._statusIcon.setRating(rating)
523
524 def setNoGo(self, reason):
525 """Set the rating of the flight to No-Go with the given reason."""
526 gobject.idle_add(self._setNoGo, reason)
527
528 def _setNoGo(self, reason):
529 """Set the rating of the flight."""
[32]530 self._statusbar.setNoGo(reason)
[31]531 self._statusIcon.setNoGo(reason)
532
[29]533 def _handleMainWindowState(self, window, event):
534 """Hande a change in the state of the window"""
535 iconified = gdk.WindowState.ICONIFIED if pygobject \
536 else gdk.WINDOW_STATE_ICONIFIED
[147]537 if self.config.hideMinimizedWindow and \
538 (event.changed_mask&iconified)!=0 and \
539 (event.new_window_state&iconified)!=0:
[35]540 self.hideMainWindow(savePosition = False)
[29]541
[182]542 def raiseCallback(self):
543 """Callback for the singleton handling code."""
544 gobject.idle_add(self.raiseMainWindow)
545
546 def raiseMainWindow(self):
547 """SHow the main window if invisible, and raise it."""
548 if not self._mainWindow.get_visible():
549 self.showMainWindow()
550 self._mainWindow.present()
551
[35]552 def hideMainWindow(self, savePosition = True):
[29]553 """Hide the main window and save its position."""
[35]554 if savePosition:
555 (self._mainWindowX, self._mainWindowY) = \
556 self._mainWindow.get_window().get_root_origin()
557 else:
558 self._mainWindowX = self._mainWindowY = None
[29]559 self._mainWindow.hide()
560 self._statusIcon.mainWindowHidden()
561 return True
562
563 def showMainWindow(self):
564 """Show the main window at its former position."""
[35]565 if self._mainWindowX is not None and self._mainWindowY is not None:
566 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
567
568 self._mainWindow.show()
[29]569 self._mainWindow.deiconify()
[35]570
[29]571 self._statusIcon.mainWindowShown()
572
573 def toggleMainWindow(self):
574 """Toggle the main window."""
575 if self._mainWindow.get_visible():
576 self.hideMainWindow()
577 else:
578 self.showMainWindow()
[36]579
[77]580 def hideMonitorWindow(self, savePosition = True):
581 """Hide the monitor window."""
582 if savePosition:
583 (self._monitorWindowX, self._monitorWindowY) = \
584 self._monitorWindow.get_window().get_root_origin()
585 else:
586 self._monitorWindowX = self._monitorWindowY = None
587 self._monitorWindow.hide()
588 self._statusIcon.monitorWindowHidden()
[93]589 if self._showMonitorMenuItem.get_active():
590 self._selfToggling = True
591 self._showMonitorMenuItem.set_active(False)
[77]592 return True
593
594 def showMonitorWindow(self):
595 """Show the monitor window."""
596 if self._monitorWindowX is not None and self._monitorWindowY is not None:
597 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
598 self._monitorWindow.show_all()
599 self._statusIcon.monitorWindowShown()
[93]600 if not self._showMonitorMenuItem.get_active():
601 self._selfToggling = True
602 self._showMonitorMenuItem.set_active(True)
603
604 def _toggleMonitorWindow(self, menuItem):
605 if self._selfToggling:
606 self._selfToggling = False
607 elif self._monitorWindow.get_visible():
608 self.hideMonitorWindow()
609 else:
610 self.showMonitorWindow()
[77]611
[38]612 def restart(self):
613 """Quit and restart the application."""
614 self.toRestart = True
[81]615 self._quit(force = True)
[38]616
617 def flushStdIO(self):
618 """Flush any text to the standard error that could not be logged."""
619 if self._stdioText:
620 sys.__stderr__.write(self._stdioText)
621
[36]622 def writeStdIO(self, text):
623 """Write the given text into standard I/O log."""
[38]624 with self._stdioLock:
625 self._stdioText += text
626
627 gobject.idle_add(self._writeStdIO)
[36]628
[49]629 def beginBusy(self, message):
630 """Begin a period of background processing."""
[93]631 self._wizard.set_sensitive(False)
[117]632 self._weightHelp.set_sensitive(False)
[49]633 self._mainWindow.get_window().set_cursor(self._busyCursor)
634 self._statusbar.updateBusyState(message)
635
636 def endBusy(self):
637 """End a period of background processing."""
638 self._mainWindow.get_window().set_cursor(None)
[117]639 self._weightHelp.set_sensitive(True)
[93]640 self._wizard.set_sensitive(True)
[49]641 self._statusbar.updateBusyState(None)
642
[117]643 def initializeWeightHelp(self):
644 """Initialize the weight help tab."""
645 self._weightHelp.reset()
646 self._weightHelp.enable()
647
[134]648 def getFleetAsync(self, callback = None, force = None):
649 """Get the fleet asynchronously."""
650 gobject.idle_add(self.getFleet, callback, force)
651
[119]652 def getFleet(self, callback = None, force = False):
653 """Get the fleet.
654
655 If force is False, and we already have a fleet retrieved,
656 that one will be used."""
657 if self._fleet is None or force:
658 self._fleetCallback = callback
659 self.beginBusy(xstr("fleet_busy"))
660 self.webHandler.getFleet(self._fleetResultCallback)
661 else:
662 callback(self._fleet)
663
664 def _fleetResultCallback(self, returned, result):
665 """Called when the fleet has been queried."""
666 gobject.idle_add(self._handleFleetResult, returned, result)
667
668 def _handleFleetResult(self, returned, result):
669 """Handle the fleet result."""
670 self.endBusy()
671 if returned:
672 self._fleet = result.fleet
673 else:
674 self._fleet = None
675
[120]676 dialog = gtk.MessageDialog(parent = self.mainWindow,
[119]677 type = MESSAGETYPE_ERROR,
678 message_format = xstr("fleet_failed"))
[130]679 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
680 dialog.set_title(WINDOW_TITLE_BASE)
681 dialog.run()
682 dialog.hide()
683
684 callback = self._fleetCallback
685 self._fleetCallback = None
686 if callback is not None:
687 callback(self._fleet)
688 self._fleetGateStatus.handleFleet(self._fleet)
689
690 def updatePlane(self, tailNumber, status,
691 gateNumber = None, callback = None):
692 """Update the status of the given plane."""
693 self.beginBusy(xstr("fleet_update_busy"))
694
695 self._updatePlaneCallback = callback
696
697 self._updatePlaneTailNumber = tailNumber
698 self._updatePlaneStatus = status
699 self._updatePlaneGateNumber = gateNumber
700
701 self.webHandler.updatePlane(self._updatePlaneResultCallback,
702 tailNumber, status, gateNumber)
703
704 def _updatePlaneResultCallback(self, returned, result):
705 """Called when the status of a plane has been updated."""
706 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
707
708 def _handleUpdatePlaneResult(self, returned, result):
709 """Handle the plane update result."""
710 self.endBusy()
711 if returned:
712 success = result.success
713 if success:
714 if self._fleet is not None:
715 self._fleet.updatePlane(self._updatePlaneTailNumber,
716 self._updatePlaneStatus,
717 self._updatePlaneGateNumber)
718 self._fleetGateStatus.handleFleet(self._fleet)
719 else:
720 dialog = gtk.MessageDialog(parent = self.mainWindow,
721 type = MESSAGETYPE_ERROR,
722 message_format = xstr("fleet_update_failed"))
[124]723 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
[119]724 dialog.set_title(WINDOW_TITLE_BASE)
725 dialog.run()
726 dialog.hide()
727
[130]728 success = None
729
730 callback = self._updatePlaneCallback
731 self._updatePlaneCallback = None
732 if callback is not None:
733 callback(success)
[119]734
[38]735 def _writeStdIO(self):
[36]736 """Perform the real writing."""
[38]737 with self._stdioLock:
738 text = self._stdioText
739 self._stdioText = ""
740 if not text: return
741
[36]742 lines = text.splitlines()
743 if text[-1]=="\n":
744 text = ""
745 else:
746 text = lines[-1]
747 lines = lines[:-1]
[203]748
749 now = datetime.datetime.now()
750 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
[36]751
752 for line in lines:
[96]753 #print >> sys.__stdout__, line
[203]754 if self._stdioStartingLine:
755 self._writeLog(timeStr, self._debugLogView)
[93]756 self._writeLog(line + "\n", self._debugLogView)
[203]757 self._stdioStartingLine = True
[36]758
759 if text:
[96]760 #print >> sys.__stdout__, text,
[203]761 if self._stdioStartingLine:
762 self._writeLog(timeStr, self._debugLogView)
[93]763 self._writeLog(text, self._debugLogView)
[203]764 self._stdioStartingLine = False
[51]765
[59]766 def connectSimulator(self, aircraftType):
767 """Connect to the simulator for the first time."""
768 self._logger.reset()
769
770 self._flight = flight.Flight(self._logger, self)
[131]771 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
[59]772 self._flight.aircraftType = aircraftType
773 self._flight.aircraft = acft.Aircraft.create(self._flight)
774 self._flight.aircraft._checkers.append(self)
775
776 if self._simulator is None:
777 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
[133]778 fs.setupMessageSending(self.config, self._simulator)
[148]779 self._setupTimeSync()
780
[59]781 self._flight.simulator = self._simulator
782
[107]783 self.beginBusy(xstr("connect_busy"))
[59]784 self._statusbar.updateConnection(self._connecting, self._connected)
785
786 self._connecting = True
787 self._simulator.connect(self._flight.aircraft)
788
[70]789 def startMonitoring(self):
790 """Start monitoring."""
[88]791 if not self._monitoring:
792 self.simulator.startMonitoring()
793 self._monitoring = True
[70]794
795 def stopMonitoring(self):
796 """Stop monitoring."""
[88]797 if self._monitoring:
798 self.simulator.stopMonitoring()
799 self._monitoring = False
[70]800
[93]801 def _buildMenuBar(self, accelGroup):
802 """Build the main menu bar."""
803 menuBar = gtk.MenuBar()
804
[110]805 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
[93]806 fileMenu = gtk.Menu()
807 fileMenuItem.set_submenu(fileMenu)
808 menuBar.append(fileMenuItem)
809
[151]810 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
811 loadPIREPMenuItem.set_use_stock(True)
812 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
813 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
814 ord(xstr("menu_file_loadPIREP_key")),
815 CONTROL_MASK, ACCEL_VISIBLE)
816 loadPIREPMenuItem.connect("activate", self._loadPIREP)
817 fileMenu.append(loadPIREPMenuItem)
818
819 fileMenu.append(gtk.SeparatorMenuItem())
820
[93]821 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
822 quitMenuItem.set_use_stock(True)
[110]823 quitMenuItem.set_label(xstr("menu_file_quit"))
[93]824 quitMenuItem.add_accelerator("activate", accelGroup,
[110]825 ord(xstr("menu_file_quit_key")),
826 CONTROL_MASK, ACCEL_VISIBLE)
[93]827 quitMenuItem.connect("activate", self._quit)
828 fileMenu.append(quitMenuItem)
829
[123]830 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
831 toolsMenu = gtk.Menu()
832 toolsMenuItem.set_submenu(toolsMenu)
833 menuBar.append(toolsMenuItem)
834
[204]835 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
[172]836 checklistMenuItem.set_use_stock(True)
837 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
838 checklistMenuItem.add_accelerator("activate", accelGroup,
839 ord(xstr("menu_tools_chklst_key")),
840 CONTROL_MASK, ACCEL_VISIBLE)
841 checklistMenuItem.connect("activate", self._editChecklist)
842 toolsMenu.append(checklistMenuItem)
843
[204]844 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
[123]845 prefsMenuItem.set_use_stock(True)
846 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
847 prefsMenuItem.add_accelerator("activate", accelGroup,
848 ord(xstr("menu_tools_prefs_key")),
849 CONTROL_MASK, ACCEL_VISIBLE)
850 prefsMenuItem.connect("activate", self._editPreferences)
851 toolsMenu.append(prefsMenuItem)
[93]852
[110]853 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
[93]854 viewMenu = gtk.Menu()
855 viewMenuItem.set_submenu(viewMenu)
856 menuBar.append(viewMenuItem)
[28]857
[93]858 self._showMonitorMenuItem = gtk.CheckMenuItem()
[110]859 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
[93]860 self._showMonitorMenuItem.set_use_underline(True)
861 self._showMonitorMenuItem.set_active(False)
862 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
[110]863 ord(xstr("menu_view_monitor_key")),
864 CONTROL_MASK, ACCEL_VISIBLE)
[93]865 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
866 viewMenu.append(self._showMonitorMenuItem)
867
868 showDebugMenuItem = gtk.CheckMenuItem()
[110]869 showDebugMenuItem.set_label(xstr("menu_view_debug"))
[93]870 showDebugMenuItem.set_use_underline(True)
871 showDebugMenuItem.set_active(False)
872 showDebugMenuItem.add_accelerator("activate", accelGroup,
[110]873 ord(xstr("menu_view_debug_key")),
874 CONTROL_MASK, ACCEL_VISIBLE)
[93]875 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
876 viewMenu.append(showDebugMenuItem)
[28]877
[93]878 return menuBar
[28]879
[93]880 def _toggleDebugLog(self, menuItem):
881 """Toggle the debug log."""
882 if menuItem.get_active():
[107]883 label = gtk.Label(xstr("tab_debug_log"))
[93]884 label.set_use_underline(True)
[110]885 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
[93]886 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
887 self._notebook.set_current_page(self._debugLogPage)
888 else:
889 self._notebook.remove_page(self._debugLogPage)
890
891 def _buildLogWidget(self):
892 """Build the widget for the log."""
893 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
894
895 alignment.set_padding(padding_top = 8, padding_bottom = 8,
896 padding_left = 16, padding_right = 16)
[28]897
898 logScroller = gtk.ScrolledWindow()
[93]899 # FIXME: these should be constants in common
900 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
901 else gtk.POLICY_AUTOMATIC,
902 gtk.PolicyType.AUTOMATIC if pygobject
903 else gtk.POLICY_AUTOMATIC)
904 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
905 else gtk.SHADOW_IN)
906 logView = gtk.TextView()
907 logView.set_editable(False)
[171]908 logView.set_cursor_visible(False)
[93]909 logScroller.add(logView)
[28]910
911 logBox = gtk.VBox()
912 logBox.pack_start(logScroller, True, True, 0)
913 logBox.set_size_request(-1, 200)
914
[93]915 alignment.add(logBox)
[28]916
[93]917 return (alignment, logView)
[28]918
[93]919 def _writeLog(self, msg, logView):
[28]920 """Write the given message to the log."""
[93]921 buffer = logView.get_buffer()
[28]922 buffer.insert(buffer.get_end_iter(), msg)
[93]923 logView.scroll_mark_onscreen(buffer.get_insert())
[28]924
[81]925 def _quit(self, what = None, force = False):
[38]926 """Quit from the application."""
[81]927 if force:
928 result=RESPONSETYPE_YES
929 else:
[105]930 dialog = gtk.MessageDialog(parent = self._mainWindow,
931 type = MESSAGETYPE_QUESTION,
[110]932 message_format = xstr("quit_question"))
933
[124]934 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
935 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
936
[105]937 dialog.set_title(WINDOW_TITLE_BASE)
[81]938 result = dialog.run()
939 dialog.hide()
[76]940
941 if result==RESPONSETYPE_YES:
942 self._statusIcon.destroy()
943 return gtk.main_quit()
[38]944
[46]945 def _notebookPageSwitch(self, notebook, page, page_num):
946 """Called when the current page of the notebook has changed."""
947 if page_num==0:
[48]948 gobject.idle_add(self._wizard.grabDefault)
[46]949 else:
950 self._mainWindow.set_default(None)
[123]951
[172]952 def _editChecklist(self, menuItem):
953 """Callback for editing the checklists."""
954 self._checklistEditor.run()
955
[123]956 def _editPreferences(self, menuItem):
957 """Callback for editing the preferences."""
[168]958 self._clearHotkeys()
[123]959 self._preferences.run(self.config)
[148]960 self._setupTimeSync()
[168]961 self._listenHotkeys()
[148]962
963 def _setupTimeSync(self):
964 """Enable or disable the simulator time synchronization based on the
965 configuration."""
966 simulator = self._simulator
967 if simulator is not None:
968 if self.config.syncFSTime:
969 simulator.enableTimeSync()
970 else:
971 simulator.disableTimeSync()
[151]972
973 def _loadPIREP(self, menuItem):
974 """Load a PIREP for sending."""
975 dialog = self._getLoadPirepDialog()
976
977 if self._lastLoadedPIREP:
978 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
979 else:
980 pirepDirectory = self.config.pirepDirectory
981 if pirepDirectory is not None:
982 dialog.set_current_folder(pirepDirectory)
983
984 result = dialog.run()
985 dialog.hide()
986
987 if result==RESPONSETYPE_OK:
[164]988 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
[151]989
990 pirep = PIREP.load(self._lastLoadedPIREP)
991 if pirep is None:
992 dialog = gtk.MessageDialog(parent = self._mainWindow,
993 type = MESSAGETYPE_ERROR,
994 message_format = xstr("loadPIREP_failed"))
995 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
996 dialog.set_title(WINDOW_TITLE_BASE)
997 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
998 dialog.run()
999 dialog.hide()
1000 else:
1001 dialog = self._getSendLoadedDialog(pirep)
1002 dialog.show_all()
1003 result = dialog.run()
1004 dialog.hide()
1005
1006 if result==RESPONSETYPE_OK:
1007 self.sendPIREP(pirep)
1008
1009 def _getLoadPirepDialog(self):
1010 """Get the PIREP loading file chooser dialog.
1011
1012 If it is not created yet, it will be created."""
1013 if self._loadPIREPDialog is None:
1014 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1015 xstr("loadPIREP_browser_title"),
1016 action = FILE_CHOOSER_ACTION_OPEN,
1017 buttons = (gtk.STOCK_CANCEL,
1018 RESPONSETYPE_CANCEL,
1019 gtk.STOCK_OK, RESPONSETYPE_OK),
1020 parent = self._mainWindow)
1021 dialog.set_modal(True)
1022
1023
1024 filter = gtk.FileFilter()
[184]1025 filter.set_name(xstr("file_filter_pireps"))
[151]1026 filter.add_pattern("*.pirep")
1027 dialog.add_filter(filter)
1028
1029 filter = gtk.FileFilter()
[184]1030 filter.set_name(xstr("file_filter_all"))
[151]1031 filter.add_pattern("*.*")
1032 dialog.add_filter(filter)
1033
1034 self._loadPIREPDialog = dialog
1035
1036 return self._loadPIREPDialog
1037
1038 def _getSendLoadedDialog(self, pirep):
1039 """Get a dialog displaying the main information of the flight from the
1040 PIREP and providing Cancel and Send buttons."""
1041 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1042 xstr("loadPIREP_send_title"),
1043 parent = self._mainWindow,
1044 flags = DIALOG_MODAL)
1045
1046 contentArea = dialog.get_content_area()
1047
1048 label = gtk.Label(xstr("loadPIREP_send_help"))
1049 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1050 xscale = 0.0, yscale = 0.0)
1051 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1052 padding_left = 48, padding_right = 48)
1053 alignment.add(label)
1054 contentArea.pack_start(alignment, False, False, 8)
1055
1056 table = gtk.Table(5, 2)
1057 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1058 xscale = 0.0, yscale = 0.0)
1059 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1060 padding_left = 48, padding_right = 48)
1061 table.set_row_spacings(4)
1062 table.set_col_spacings(16)
1063 tableAlignment.add(table)
1064 contentArea.pack_start(tableAlignment, True, True, 8)
1065
1066 bookedFlight = pirep.bookedFlight
1067
1068 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1069 label.set_use_markup(True)
1070 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1071 xscale = 0.0, yscale = 0.0)
1072 labelAlignment.add(label)
1073 table.attach(labelAlignment, 0, 1, 0, 1)
1074
1075 label = gtk.Label(bookedFlight.callsign)
1076 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1077 xscale = 0.0, yscale = 0.0)
1078 labelAlignment.add(label)
1079 table.attach(labelAlignment, 1, 2, 0, 1)
1080
1081 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1082 label.set_use_markup(True)
1083 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1084 xscale = 0.0, yscale = 0.0)
1085 labelAlignment.add(label)
1086 table.attach(labelAlignment, 0, 1, 1, 2)
1087
1088 label = gtk.Label(str(bookedFlight.departureTime.date()))
1089 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1090 xscale = 0.0, yscale = 0.0)
1091 labelAlignment.add(label)
1092 table.attach(labelAlignment, 1, 2, 1, 2)
1093
1094 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1095 label.set_use_markup(True)
1096 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1097 xscale = 0.0, yscale = 0.0)
1098 labelAlignment.add(label)
1099 table.attach(labelAlignment, 0, 1, 2, 3)
1100
1101 label = gtk.Label(bookedFlight.departureICAO)
1102 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1103 xscale = 0.0, yscale = 0.0)
1104 labelAlignment.add(label)
1105 table.attach(labelAlignment, 1, 2, 2, 3)
1106
1107 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1108 label.set_use_markup(True)
1109 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1110 xscale = 0.0, yscale = 0.0)
1111 labelAlignment.add(label)
1112 table.attach(labelAlignment, 0, 1, 3, 4)
1113
1114 label = gtk.Label(bookedFlight.arrivalICAO)
1115 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1116 xscale = 0.0, yscale = 0.0)
1117 labelAlignment.add(label)
1118 table.attach(labelAlignment, 1, 2, 3, 4)
1119
1120 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1121 label.set_use_markup(True)
1122 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1123 xscale = 0.0, yscale = 0.0)
1124 labelAlignment.add(label)
1125 table.attach(labelAlignment, 0, 1, 4, 5)
1126
1127 rating = pirep.rating
1128 label = gtk.Label()
1129 if rating<0:
1130 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1131 else:
1132 label.set_text("%.1f %%" % (rating,))
1133
1134 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1135 xscale = 0.0, yscale = 0.0)
1136 labelAlignment.add(label)
1137 table.attach(labelAlignment, 1, 2, 4, 5)
1138
1139 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1140 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1141
1142 return dialog
1143
1144 def sendPIREP(self, pirep, callback = None):
1145 """Send the given PIREP."""
1146 self.beginBusy(xstr("sendPIREP_busy"))
1147 self._sendPIREPCallback = callback
1148 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1149
1150 def _pirepSentCallback(self, returned, result):
1151 """Callback for the PIREP sending result."""
1152 gobject.idle_add(self._handlePIREPSent, returned, result)
1153
1154 def _handlePIREPSent(self, returned, result):
1155 """Callback for the PIREP sending result."""
1156 self.endBusy()
1157 secondaryMarkup = None
1158 type = MESSAGETYPE_ERROR
1159 if returned:
1160 if result.success:
1161 type = MESSAGETYPE_INFO
1162 messageFormat = xstr("sendPIREP_success")
1163 secondaryMarkup = xstr("sendPIREP_success_sec")
1164 elif result.alreadyFlown:
1165 messageFormat = xstr("sendPIREP_already")
1166 secondaryMarkup = xstr("sendPIREP_already_sec")
1167 elif result.notAvailable:
1168 messageFormat = xstr("sendPIREP_notavail")
1169 else:
1170 messageFormat = xstr("sendPIREP_unknown")
1171 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1172 else:
1173 print "PIREP sending failed", result
1174 messageFormat = xstr("sendPIREP_failed")
1175 secondaryMarkup = xstr("sendPIREP_failed_sec")
1176
1177 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1178 type = type, message_format = messageFormat)
1179 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1180 dialog.set_title(WINDOW_TITLE_BASE)
1181 if secondaryMarkup is not None:
1182 dialog.format_secondary_markup(secondaryMarkup)
1183
1184 dialog.run()
1185 dialog.hide()
1186
1187 callback = self._sendPIREPCallback
1188 self._sendPIREPCallback = None
1189 if callback is not None:
1190 callback(returned, result)
[168]1191
1192 def _listenHotkeys(self):
1193 """Setup the hotkeys based on the configuration."""
1194 if self._hotkeySetID is None and self._simulator is not None:
[178]1195 self._pilotHotkeyIndex = None
1196 self._checklistHotkeyIndex = None
1197
1198 hotkeys = []
1199
1200 config = self.config
1201 if config.enableSounds and config.pilotControlsSounds:
1202 self._pilotHotkeyIndex = len(hotkeys)
1203 hotkeys.append(config.pilotHotkey)
1204
1205 if config.enableChecklists:
1206 self._checklistHotkeyIndex = len(hotkeys)
1207 hotkeys.append(config.checklistHotkey)
1208
1209 if hotkeys:
1210 self._hotkeySetID = \
1211 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
[168]1212
1213 def _clearHotkeys(self):
1214 """Clear the hotkeys."""
1215 if self._hotkeySetID is not None:
1216 self._hotkeySetID=None
1217 self._simulator.clearHotkeys()
1218
1219 def _handleHotkeys(self, id, hotkeys):
1220 """Handle the hotkeys."""
1221 if id==self._hotkeySetID:
[170]1222 for index in hotkeys:
[178]1223 if index==self._pilotHotkeyIndex:
1224 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
[170]1225 self._flight.pilotHotkeyPressed()
[178]1226 elif index==self._checklistHotkeyIndex:
1227 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1228 self._flight.checklistHotkeyPressed()
[170]1229 else:
[178]1230 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
Note: See TracBrowser for help on using the repository browser.