source: src/mlx/gui/gui.py@ 221:2126271cea84

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

Implemented the PIREP viewer tabs for the comments and the log

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