source: src/mlx/gui/gui.py@ 203:bc3c9d58a921

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

Added timestamping to the debug log

File size: 43.4 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
[91]404 def reset(self):
405 """Reset the GUI."""
406 self._disconnect()
[92]407
[91]408 self._flightInfo.reset()
[93]409 self._flightInfo.disable()
[91]410 self.resetFlightStatus()
[28]411
[117]412 self._weightHelp.reset()
413 self._weightHelp.disable()
[92]414 self._wizard.reset()
415 self._notebook.set_current_page(0)
416
417 self._logView.get_buffer().set_text("")
418
[152]419 def _disconnect(self, closingMessage = None, duration = 3):
[91]420 """Disconnect from the simulator if connected."""
[92]421 self.stopMonitoring()
[168]422 self._clearHotkeys()
[92]423
[91]424 if self._connected:
[152]425 if closingMessage is None:
426 self._flight.simulator.disconnect()
427 else:
428 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
429 closingMessage, duration,
430 disconnect = True)
[91]431 self._connected = False
432
433 self._connecting = False
434 self._reconnecting = False
435 self._statusbar.updateConnection(False, False)
[128]436 self._weightHelp.disable()
[134]437
438 return True
[91]439
[96]440 def addFlightLogLine(self, timeStr, line):
441 """Write the given message line to the log."""
442 gobject.idle_add(self._writeLog,
443 GUI._formatFlightLogLine(timeStr, line),
444 self._logView)
445
446 def updateFlightLogLine(self, index, timeStr, line):
447 """Update the line with the given index."""
448 gobject.idle_add(self._updateFlightLogLine, index,
449 GUI._formatFlightLogLine(timeStr, line))
450
451 def _updateFlightLogLine(self, index, line):
452 """Replace the contents of the given line in the log."""
453 buffer = self._logView.get_buffer()
454 startIter = buffer.get_iter_at_line(index)
455 endIter = buffer.get_iter_at_line(index + 1)
456 buffer.delete(startIter, endIter)
457 buffer.insert(startIter, line)
458 self._logView.scroll_mark_onscreen(buffer.get_insert())
459
[28]460 def check(self, flight, aircraft, logger, oldState, state):
461 """Update the data."""
[77]462 gobject.idle_add(self._monitorWindow.setData, state)
[80]463 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
[28]464
[31]465 def resetFlightStatus(self):
466 """Reset the status of the flight."""
[32]467 self._statusbar.resetFlightStatus()
[80]468 self._statusbar.updateTime()
[31]469 self._statusIcon.resetFlightStatus()
470
471 def setStage(self, stage):
472 """Set the stage of the flight."""
473 gobject.idle_add(self._setStage, stage)
474
475 def _setStage(self, stage):
476 """Set the stage of the flight."""
[32]477 self._statusbar.setStage(stage)
[31]478 self._statusIcon.setStage(stage)
[84]479 self._wizard.setStage(stage)
[88]480 if stage==const.STAGE_END:
[152]481 self._disconnect(closingMessage =
482 "Flight plan closed. Welcome to %s" % \
483 (self.bookedFlight.arrivalICAO,),
484 duration = 5)
[31]485
486 def setRating(self, rating):
487 """Set the rating of the flight."""
488 gobject.idle_add(self._setRating, rating)
489
490 def _setRating(self, rating):
491 """Set the rating of the flight."""
[32]492 self._statusbar.setRating(rating)
[31]493 self._statusIcon.setRating(rating)
494
495 def setNoGo(self, reason):
496 """Set the rating of the flight to No-Go with the given reason."""
497 gobject.idle_add(self._setNoGo, reason)
498
499 def _setNoGo(self, reason):
500 """Set the rating of the flight."""
[32]501 self._statusbar.setNoGo(reason)
[31]502 self._statusIcon.setNoGo(reason)
503
[29]504 def _handleMainWindowState(self, window, event):
505 """Hande a change in the state of the window"""
506 iconified = gdk.WindowState.ICONIFIED if pygobject \
507 else gdk.WINDOW_STATE_ICONIFIED
[147]508 if self.config.hideMinimizedWindow and \
509 (event.changed_mask&iconified)!=0 and \
510 (event.new_window_state&iconified)!=0:
[35]511 self.hideMainWindow(savePosition = False)
[29]512
[182]513 def raiseCallback(self):
514 """Callback for the singleton handling code."""
515 gobject.idle_add(self.raiseMainWindow)
516
517 def raiseMainWindow(self):
518 """SHow the main window if invisible, and raise it."""
519 if not self._mainWindow.get_visible():
520 self.showMainWindow()
521 self._mainWindow.present()
522
[35]523 def hideMainWindow(self, savePosition = True):
[29]524 """Hide the main window and save its position."""
[35]525 if savePosition:
526 (self._mainWindowX, self._mainWindowY) = \
527 self._mainWindow.get_window().get_root_origin()
528 else:
529 self._mainWindowX = self._mainWindowY = None
[29]530 self._mainWindow.hide()
531 self._statusIcon.mainWindowHidden()
532 return True
533
534 def showMainWindow(self):
535 """Show the main window at its former position."""
[35]536 if self._mainWindowX is not None and self._mainWindowY is not None:
537 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
538
539 self._mainWindow.show()
[29]540 self._mainWindow.deiconify()
[35]541
[29]542 self._statusIcon.mainWindowShown()
543
544 def toggleMainWindow(self):
545 """Toggle the main window."""
546 if self._mainWindow.get_visible():
547 self.hideMainWindow()
548 else:
549 self.showMainWindow()
[36]550
[77]551 def hideMonitorWindow(self, savePosition = True):
552 """Hide the monitor window."""
553 if savePosition:
554 (self._monitorWindowX, self._monitorWindowY) = \
555 self._monitorWindow.get_window().get_root_origin()
556 else:
557 self._monitorWindowX = self._monitorWindowY = None
558 self._monitorWindow.hide()
559 self._statusIcon.monitorWindowHidden()
[93]560 if self._showMonitorMenuItem.get_active():
561 self._selfToggling = True
562 self._showMonitorMenuItem.set_active(False)
[77]563 return True
564
565 def showMonitorWindow(self):
566 """Show the monitor window."""
567 if self._monitorWindowX is not None and self._monitorWindowY is not None:
568 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
569 self._monitorWindow.show_all()
570 self._statusIcon.monitorWindowShown()
[93]571 if not self._showMonitorMenuItem.get_active():
572 self._selfToggling = True
573 self._showMonitorMenuItem.set_active(True)
574
575 def _toggleMonitorWindow(self, menuItem):
576 if self._selfToggling:
577 self._selfToggling = False
578 elif self._monitorWindow.get_visible():
579 self.hideMonitorWindow()
580 else:
581 self.showMonitorWindow()
[77]582
[38]583 def restart(self):
584 """Quit and restart the application."""
585 self.toRestart = True
[81]586 self._quit(force = True)
[38]587
588 def flushStdIO(self):
589 """Flush any text to the standard error that could not be logged."""
590 if self._stdioText:
591 sys.__stderr__.write(self._stdioText)
592
[36]593 def writeStdIO(self, text):
594 """Write the given text into standard I/O log."""
[38]595 with self._stdioLock:
596 self._stdioText += text
597
598 gobject.idle_add(self._writeStdIO)
[36]599
[49]600 def beginBusy(self, message):
601 """Begin a period of background processing."""
[93]602 self._wizard.set_sensitive(False)
[117]603 self._weightHelp.set_sensitive(False)
[49]604 self._mainWindow.get_window().set_cursor(self._busyCursor)
605 self._statusbar.updateBusyState(message)
606
607 def endBusy(self):
608 """End a period of background processing."""
609 self._mainWindow.get_window().set_cursor(None)
[117]610 self._weightHelp.set_sensitive(True)
[93]611 self._wizard.set_sensitive(True)
[49]612 self._statusbar.updateBusyState(None)
613
[117]614 def initializeWeightHelp(self):
615 """Initialize the weight help tab."""
616 self._weightHelp.reset()
617 self._weightHelp.enable()
618
[134]619 def getFleetAsync(self, callback = None, force = None):
620 """Get the fleet asynchronously."""
621 gobject.idle_add(self.getFleet, callback, force)
622
[119]623 def getFleet(self, callback = None, force = False):
624 """Get the fleet.
625
626 If force is False, and we already have a fleet retrieved,
627 that one will be used."""
628 if self._fleet is None or force:
629 self._fleetCallback = callback
630 self.beginBusy(xstr("fleet_busy"))
631 self.webHandler.getFleet(self._fleetResultCallback)
632 else:
633 callback(self._fleet)
634
635 def _fleetResultCallback(self, returned, result):
636 """Called when the fleet has been queried."""
637 gobject.idle_add(self._handleFleetResult, returned, result)
638
639 def _handleFleetResult(self, returned, result):
640 """Handle the fleet result."""
641 self.endBusy()
642 if returned:
643 self._fleet = result.fleet
644 else:
645 self._fleet = None
646
[120]647 dialog = gtk.MessageDialog(parent = self.mainWindow,
[119]648 type = MESSAGETYPE_ERROR,
649 message_format = xstr("fleet_failed"))
[130]650 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
651 dialog.set_title(WINDOW_TITLE_BASE)
652 dialog.run()
653 dialog.hide()
654
655 callback = self._fleetCallback
656 self._fleetCallback = None
657 if callback is not None:
658 callback(self._fleet)
659 self._fleetGateStatus.handleFleet(self._fleet)
660
661 def updatePlane(self, tailNumber, status,
662 gateNumber = None, callback = None):
663 """Update the status of the given plane."""
664 self.beginBusy(xstr("fleet_update_busy"))
665
666 self._updatePlaneCallback = callback
667
668 self._updatePlaneTailNumber = tailNumber
669 self._updatePlaneStatus = status
670 self._updatePlaneGateNumber = gateNumber
671
672 self.webHandler.updatePlane(self._updatePlaneResultCallback,
673 tailNumber, status, gateNumber)
674
675 def _updatePlaneResultCallback(self, returned, result):
676 """Called when the status of a plane has been updated."""
677 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
678
679 def _handleUpdatePlaneResult(self, returned, result):
680 """Handle the plane update result."""
681 self.endBusy()
682 if returned:
683 success = result.success
684 if success:
685 if self._fleet is not None:
686 self._fleet.updatePlane(self._updatePlaneTailNumber,
687 self._updatePlaneStatus,
688 self._updatePlaneGateNumber)
689 self._fleetGateStatus.handleFleet(self._fleet)
690 else:
691 dialog = gtk.MessageDialog(parent = self.mainWindow,
692 type = MESSAGETYPE_ERROR,
693 message_format = xstr("fleet_update_failed"))
[124]694 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
[119]695 dialog.set_title(WINDOW_TITLE_BASE)
696 dialog.run()
697 dialog.hide()
698
[130]699 success = None
700
701 callback = self._updatePlaneCallback
702 self._updatePlaneCallback = None
703 if callback is not None:
704 callback(success)
[119]705
[38]706 def _writeStdIO(self):
[36]707 """Perform the real writing."""
[38]708 with self._stdioLock:
709 text = self._stdioText
710 self._stdioText = ""
711 if not text: return
712
[36]713 lines = text.splitlines()
714 if text[-1]=="\n":
715 text = ""
716 else:
717 text = lines[-1]
718 lines = lines[:-1]
[203]719
720 now = datetime.datetime.now()
721 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
[36]722
723 for line in lines:
[96]724 #print >> sys.__stdout__, line
[203]725 if self._stdioStartingLine:
726 self._writeLog(timeStr, self._debugLogView)
[93]727 self._writeLog(line + "\n", self._debugLogView)
[203]728 self._stdioStartingLine = True
[36]729
730 if text:
[96]731 #print >> sys.__stdout__, text,
[203]732 if self._stdioStartingLine:
733 self._writeLog(timeStr, self._debugLogView)
[93]734 self._writeLog(text, self._debugLogView)
[203]735 self._stdioStartingLine = False
[51]736
[59]737 def connectSimulator(self, aircraftType):
738 """Connect to the simulator for the first time."""
739 self._logger.reset()
740
741 self._flight = flight.Flight(self._logger, self)
[131]742 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
[59]743 self._flight.aircraftType = aircraftType
744 self._flight.aircraft = acft.Aircraft.create(self._flight)
745 self._flight.aircraft._checkers.append(self)
746
747 if self._simulator is None:
748 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
[133]749 fs.setupMessageSending(self.config, self._simulator)
[148]750 self._setupTimeSync()
751
[59]752 self._flight.simulator = self._simulator
753
[107]754 self.beginBusy(xstr("connect_busy"))
[59]755 self._statusbar.updateConnection(self._connecting, self._connected)
756
757 self._connecting = True
758 self._simulator.connect(self._flight.aircraft)
759
[70]760 def startMonitoring(self):
761 """Start monitoring."""
[88]762 if not self._monitoring:
763 self.simulator.startMonitoring()
764 self._monitoring = True
[70]765
766 def stopMonitoring(self):
767 """Stop monitoring."""
[88]768 if self._monitoring:
769 self.simulator.stopMonitoring()
770 self._monitoring = False
[70]771
[93]772 def _buildMenuBar(self, accelGroup):
773 """Build the main menu bar."""
774 menuBar = gtk.MenuBar()
775
[110]776 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
[93]777 fileMenu = gtk.Menu()
778 fileMenuItem.set_submenu(fileMenu)
779 menuBar.append(fileMenuItem)
780
[151]781 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
782 loadPIREPMenuItem.set_use_stock(True)
783 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
784 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
785 ord(xstr("menu_file_loadPIREP_key")),
786 CONTROL_MASK, ACCEL_VISIBLE)
787 loadPIREPMenuItem.connect("activate", self._loadPIREP)
788 fileMenu.append(loadPIREPMenuItem)
789
790 fileMenu.append(gtk.SeparatorMenuItem())
791
[93]792 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
793 quitMenuItem.set_use_stock(True)
[110]794 quitMenuItem.set_label(xstr("menu_file_quit"))
[93]795 quitMenuItem.add_accelerator("activate", accelGroup,
[110]796 ord(xstr("menu_file_quit_key")),
797 CONTROL_MASK, ACCEL_VISIBLE)
[93]798 quitMenuItem.connect("activate", self._quit)
799 fileMenu.append(quitMenuItem)
800
[123]801 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
802 toolsMenu = gtk.Menu()
803 toolsMenuItem.set_submenu(toolsMenu)
804 menuBar.append(toolsMenuItem)
805
[172]806 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
807 checklistMenuItem.set_use_stock(True)
808 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
809 checklistMenuItem.add_accelerator("activate", accelGroup,
810 ord(xstr("menu_tools_chklst_key")),
811 CONTROL_MASK, ACCEL_VISIBLE)
812 checklistMenuItem.connect("activate", self._editChecklist)
813 toolsMenu.append(checklistMenuItem)
814
815 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
[123]816 prefsMenuItem.set_use_stock(True)
817 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
818 prefsMenuItem.add_accelerator("activate", accelGroup,
819 ord(xstr("menu_tools_prefs_key")),
820 CONTROL_MASK, ACCEL_VISIBLE)
821 prefsMenuItem.connect("activate", self._editPreferences)
822 toolsMenu.append(prefsMenuItem)
[93]823
[110]824 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
[93]825 viewMenu = gtk.Menu()
826 viewMenuItem.set_submenu(viewMenu)
827 menuBar.append(viewMenuItem)
[28]828
[93]829 self._showMonitorMenuItem = gtk.CheckMenuItem()
[110]830 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
[93]831 self._showMonitorMenuItem.set_use_underline(True)
832 self._showMonitorMenuItem.set_active(False)
833 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
[110]834 ord(xstr("menu_view_monitor_key")),
835 CONTROL_MASK, ACCEL_VISIBLE)
[93]836 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
837 viewMenu.append(self._showMonitorMenuItem)
838
839 showDebugMenuItem = gtk.CheckMenuItem()
[110]840 showDebugMenuItem.set_label(xstr("menu_view_debug"))
[93]841 showDebugMenuItem.set_use_underline(True)
842 showDebugMenuItem.set_active(False)
843 showDebugMenuItem.add_accelerator("activate", accelGroup,
[110]844 ord(xstr("menu_view_debug_key")),
845 CONTROL_MASK, ACCEL_VISIBLE)
[93]846 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
847 viewMenu.append(showDebugMenuItem)
[28]848
[93]849 return menuBar
[28]850
[93]851 def _toggleDebugLog(self, menuItem):
852 """Toggle the debug log."""
853 if menuItem.get_active():
[107]854 label = gtk.Label(xstr("tab_debug_log"))
[93]855 label.set_use_underline(True)
[110]856 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
[93]857 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
858 self._notebook.set_current_page(self._debugLogPage)
859 else:
860 self._notebook.remove_page(self._debugLogPage)
861
862 def _buildLogWidget(self):
863 """Build the widget for the log."""
864 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
865
866 alignment.set_padding(padding_top = 8, padding_bottom = 8,
867 padding_left = 16, padding_right = 16)
[28]868
869 logScroller = gtk.ScrolledWindow()
[93]870 # FIXME: these should be constants in common
871 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
872 else gtk.POLICY_AUTOMATIC,
873 gtk.PolicyType.AUTOMATIC if pygobject
874 else gtk.POLICY_AUTOMATIC)
875 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
876 else gtk.SHADOW_IN)
877 logView = gtk.TextView()
878 logView.set_editable(False)
[171]879 logView.set_cursor_visible(False)
[93]880 logScroller.add(logView)
[28]881
882 logBox = gtk.VBox()
883 logBox.pack_start(logScroller, True, True, 0)
884 logBox.set_size_request(-1, 200)
885
[93]886 alignment.add(logBox)
[28]887
[93]888 return (alignment, logView)
[28]889
[93]890 def _writeLog(self, msg, logView):
[28]891 """Write the given message to the log."""
[93]892 buffer = logView.get_buffer()
[28]893 buffer.insert(buffer.get_end_iter(), msg)
[93]894 logView.scroll_mark_onscreen(buffer.get_insert())
[28]895
[81]896 def _quit(self, what = None, force = False):
[38]897 """Quit from the application."""
[81]898 if force:
899 result=RESPONSETYPE_YES
900 else:
[105]901 dialog = gtk.MessageDialog(parent = self._mainWindow,
902 type = MESSAGETYPE_QUESTION,
[110]903 message_format = xstr("quit_question"))
904
[124]905 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
906 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
907
[105]908 dialog.set_title(WINDOW_TITLE_BASE)
[81]909 result = dialog.run()
910 dialog.hide()
[76]911
912 if result==RESPONSETYPE_YES:
913 self._statusIcon.destroy()
914 return gtk.main_quit()
[38]915
[46]916 def _notebookPageSwitch(self, notebook, page, page_num):
917 """Called when the current page of the notebook has changed."""
918 if page_num==0:
[48]919 gobject.idle_add(self._wizard.grabDefault)
[46]920 else:
921 self._mainWindow.set_default(None)
[123]922
[172]923 def _editChecklist(self, menuItem):
924 """Callback for editing the checklists."""
925 self._checklistEditor.run()
926
[123]927 def _editPreferences(self, menuItem):
928 """Callback for editing the preferences."""
[168]929 self._clearHotkeys()
[123]930 self._preferences.run(self.config)
[148]931 self._setupTimeSync()
[168]932 self._listenHotkeys()
[148]933
934 def _setupTimeSync(self):
935 """Enable or disable the simulator time synchronization based on the
936 configuration."""
937 simulator = self._simulator
938 if simulator is not None:
939 if self.config.syncFSTime:
940 simulator.enableTimeSync()
941 else:
942 simulator.disableTimeSync()
[151]943
944 def _loadPIREP(self, menuItem):
945 """Load a PIREP for sending."""
946 dialog = self._getLoadPirepDialog()
947
948 if self._lastLoadedPIREP:
949 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
950 else:
951 pirepDirectory = self.config.pirepDirectory
952 if pirepDirectory is not None:
953 dialog.set_current_folder(pirepDirectory)
954
955 result = dialog.run()
956 dialog.hide()
957
958 if result==RESPONSETYPE_OK:
[164]959 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
[151]960
961 pirep = PIREP.load(self._lastLoadedPIREP)
962 if pirep is None:
963 dialog = gtk.MessageDialog(parent = self._mainWindow,
964 type = MESSAGETYPE_ERROR,
965 message_format = xstr("loadPIREP_failed"))
966 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
967 dialog.set_title(WINDOW_TITLE_BASE)
968 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
969 dialog.run()
970 dialog.hide()
971 else:
972 dialog = self._getSendLoadedDialog(pirep)
973 dialog.show_all()
974 result = dialog.run()
975 dialog.hide()
976
977 if result==RESPONSETYPE_OK:
978 self.sendPIREP(pirep)
979
980 def _getLoadPirepDialog(self):
981 """Get the PIREP loading file chooser dialog.
982
983 If it is not created yet, it will be created."""
984 if self._loadPIREPDialog is None:
985 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
986 xstr("loadPIREP_browser_title"),
987 action = FILE_CHOOSER_ACTION_OPEN,
988 buttons = (gtk.STOCK_CANCEL,
989 RESPONSETYPE_CANCEL,
990 gtk.STOCK_OK, RESPONSETYPE_OK),
991 parent = self._mainWindow)
992 dialog.set_modal(True)
993
994
995 filter = gtk.FileFilter()
[184]996 filter.set_name(xstr("file_filter_pireps"))
[151]997 filter.add_pattern("*.pirep")
998 dialog.add_filter(filter)
999
1000 filter = gtk.FileFilter()
[184]1001 filter.set_name(xstr("file_filter_all"))
[151]1002 filter.add_pattern("*.*")
1003 dialog.add_filter(filter)
1004
1005 self._loadPIREPDialog = dialog
1006
1007 return self._loadPIREPDialog
1008
1009 def _getSendLoadedDialog(self, pirep):
1010 """Get a dialog displaying the main information of the flight from the
1011 PIREP and providing Cancel and Send buttons."""
1012 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1013 xstr("loadPIREP_send_title"),
1014 parent = self._mainWindow,
1015 flags = DIALOG_MODAL)
1016
1017 contentArea = dialog.get_content_area()
1018
1019 label = gtk.Label(xstr("loadPIREP_send_help"))
1020 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1021 xscale = 0.0, yscale = 0.0)
1022 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1023 padding_left = 48, padding_right = 48)
1024 alignment.add(label)
1025 contentArea.pack_start(alignment, False, False, 8)
1026
1027 table = gtk.Table(5, 2)
1028 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1029 xscale = 0.0, yscale = 0.0)
1030 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1031 padding_left = 48, padding_right = 48)
1032 table.set_row_spacings(4)
1033 table.set_col_spacings(16)
1034 tableAlignment.add(table)
1035 contentArea.pack_start(tableAlignment, True, True, 8)
1036
1037 bookedFlight = pirep.bookedFlight
1038
1039 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1040 label.set_use_markup(True)
1041 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1042 xscale = 0.0, yscale = 0.0)
1043 labelAlignment.add(label)
1044 table.attach(labelAlignment, 0, 1, 0, 1)
1045
1046 label = gtk.Label(bookedFlight.callsign)
1047 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1048 xscale = 0.0, yscale = 0.0)
1049 labelAlignment.add(label)
1050 table.attach(labelAlignment, 1, 2, 0, 1)
1051
1052 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1053 label.set_use_markup(True)
1054 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1055 xscale = 0.0, yscale = 0.0)
1056 labelAlignment.add(label)
1057 table.attach(labelAlignment, 0, 1, 1, 2)
1058
1059 label = gtk.Label(str(bookedFlight.departureTime.date()))
1060 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1061 xscale = 0.0, yscale = 0.0)
1062 labelAlignment.add(label)
1063 table.attach(labelAlignment, 1, 2, 1, 2)
1064
1065 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1066 label.set_use_markup(True)
1067 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1068 xscale = 0.0, yscale = 0.0)
1069 labelAlignment.add(label)
1070 table.attach(labelAlignment, 0, 1, 2, 3)
1071
1072 label = gtk.Label(bookedFlight.departureICAO)
1073 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1074 xscale = 0.0, yscale = 0.0)
1075 labelAlignment.add(label)
1076 table.attach(labelAlignment, 1, 2, 2, 3)
1077
1078 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1079 label.set_use_markup(True)
1080 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1081 xscale = 0.0, yscale = 0.0)
1082 labelAlignment.add(label)
1083 table.attach(labelAlignment, 0, 1, 3, 4)
1084
1085 label = gtk.Label(bookedFlight.arrivalICAO)
1086 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1087 xscale = 0.0, yscale = 0.0)
1088 labelAlignment.add(label)
1089 table.attach(labelAlignment, 1, 2, 3, 4)
1090
1091 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1092 label.set_use_markup(True)
1093 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1094 xscale = 0.0, yscale = 0.0)
1095 labelAlignment.add(label)
1096 table.attach(labelAlignment, 0, 1, 4, 5)
1097
1098 rating = pirep.rating
1099 label = gtk.Label()
1100 if rating<0:
1101 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1102 else:
1103 label.set_text("%.1f %%" % (rating,))
1104
1105 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1106 xscale = 0.0, yscale = 0.0)
1107 labelAlignment.add(label)
1108 table.attach(labelAlignment, 1, 2, 4, 5)
1109
1110 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1111 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1112
1113 return dialog
1114
1115 def sendPIREP(self, pirep, callback = None):
1116 """Send the given PIREP."""
1117 self.beginBusy(xstr("sendPIREP_busy"))
1118 self._sendPIREPCallback = callback
1119 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1120
1121 def _pirepSentCallback(self, returned, result):
1122 """Callback for the PIREP sending result."""
1123 gobject.idle_add(self._handlePIREPSent, returned, result)
1124
1125 def _handlePIREPSent(self, returned, result):
1126 """Callback for the PIREP sending result."""
1127 self.endBusy()
1128 secondaryMarkup = None
1129 type = MESSAGETYPE_ERROR
1130 if returned:
1131 if result.success:
1132 type = MESSAGETYPE_INFO
1133 messageFormat = xstr("sendPIREP_success")
1134 secondaryMarkup = xstr("sendPIREP_success_sec")
1135 elif result.alreadyFlown:
1136 messageFormat = xstr("sendPIREP_already")
1137 secondaryMarkup = xstr("sendPIREP_already_sec")
1138 elif result.notAvailable:
1139 messageFormat = xstr("sendPIREP_notavail")
1140 else:
1141 messageFormat = xstr("sendPIREP_unknown")
1142 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1143 else:
1144 print "PIREP sending failed", result
1145 messageFormat = xstr("sendPIREP_failed")
1146 secondaryMarkup = xstr("sendPIREP_failed_sec")
1147
1148 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1149 type = type, message_format = messageFormat)
1150 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1151 dialog.set_title(WINDOW_TITLE_BASE)
1152 if secondaryMarkup is not None:
1153 dialog.format_secondary_markup(secondaryMarkup)
1154
1155 dialog.run()
1156 dialog.hide()
1157
1158 callback = self._sendPIREPCallback
1159 self._sendPIREPCallback = None
1160 if callback is not None:
1161 callback(returned, result)
[168]1162
1163 def _listenHotkeys(self):
1164 """Setup the hotkeys based on the configuration."""
1165 if self._hotkeySetID is None and self._simulator is not None:
[178]1166 self._pilotHotkeyIndex = None
1167 self._checklistHotkeyIndex = None
1168
1169 hotkeys = []
1170
1171 config = self.config
1172 if config.enableSounds and config.pilotControlsSounds:
1173 self._pilotHotkeyIndex = len(hotkeys)
1174 hotkeys.append(config.pilotHotkey)
1175
1176 if config.enableChecklists:
1177 self._checklistHotkeyIndex = len(hotkeys)
1178 hotkeys.append(config.checklistHotkey)
1179
1180 if hotkeys:
1181 self._hotkeySetID = \
1182 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
[168]1183
1184 def _clearHotkeys(self):
1185 """Clear the hotkeys."""
1186 if self._hotkeySetID is not None:
1187 self._hotkeySetID=None
1188 self._simulator.clearHotkeys()
1189
1190 def _handleHotkeys(self, id, hotkeys):
1191 """Handle the hotkeys."""
1192 if id==self._hotkeySetID:
[170]1193 for index in hotkeys:
[178]1194 if index==self._pilotHotkeyIndex:
1195 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
[170]1196 self._flight.pilotHotkeyPressed()
[178]1197 elif index==self._checklistHotkeyIndex:
1198 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1199 self._flight.checklistHotkeyPressed()
[170]1200 else:
[178]1201 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
Note: See TracBrowser for help on using the repository browser.