source: src/mlx/gui/gui.py@ 226:473bbc9d355f

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

Faults are logged highlighted

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