source: src/mlx/gui/gui.py@ 184:0a000ef19c3a

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

Added support for the entrance exam

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