source: src/mlx/gui/gui.py@ 220:96ad81e11b85

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

The Data tab of the PIREP viewer works

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