source: src/mlx/gui/gui.py@ 225:298b775161db

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

Started the possibilities to log faults differently

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