source: src/mlx/gui/gui.py@ 208:22ff615383e9

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

It is now possible to cancel a flight and to start a new one at the end and also to refresh the list of flights.

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