source: src/mlx/gui/gui.py@ 226:473bbc9d355f

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

Faults are logged highlighted

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 addFaultTag(self._logView.get_buffer())
115 label = gtk.Label(xstr("tab_log"))
116 label.set_use_underline(True)
117 label.set_tooltip_text(xstr("tab_log_tooltip"))
118 self._notebook.append_page(logWidget, label)
119
120 self._fleetGateStatus = FleetGateStatus(self)
121 label = gtk.Label(xstr("tab_gates"))
122 label.set_use_underline(True)
123 label.set_tooltip_text(xstr("tab_gates_tooltip"))
124 self._notebook.append_page(self._fleetGateStatus, label)
125
126 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
127 self._debugLogWidget.show_all()
128
129 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
130
131 self._statusbar = Statusbar()
132 mainVBox.pack_start(self._statusbar, False, False, 0)
133
134 self._notebook.connect("switch-page", self._notebookPageSwitch)
135
136 self._monitorWindow = MonitorWindow(self, iconDirectory)
137 self._monitorWindow.add_accel_group(accelGroup)
138 self._monitorWindowX = None
139 self._monitorWindowY = None
140 self._selfToggling = False
141
142 self._pirepViewer = PIREPViewer(self)
143
144 window.show_all()
145 self._wizard.grabDefault()
146 self._weightHelp.reset()
147 self._weightHelp.disable()
148
149 self._statusIcon = StatusIcon(iconDirectory, self)
150
151 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
152 else gdk.WATCH)
153
154 self._loadPIREPDialog = None
155 self._lastLoadedPIREP = None
156
157 self._hotkeySetID = None
158 self._pilotHotkeyIndex = None
159 self._checklistHotkeyIndex = None
160
161 @property
162 def mainWindow(self):
163 """Get the main window of the GUI."""
164 return self._mainWindow
165
166 @property
167 def logger(self):
168 """Get the logger used by us."""
169 return self._logger
170
171 @property
172 def simulator(self):
173 """Get the simulator used by us."""
174 return self._simulator
175
176 @property
177 def flight(self):
178 """Get the flight being performed."""
179 return self._flight
180
181 @property
182 def entranceExam(self):
183 """Get whether an entrance exam is about to be taken."""
184 return self._wizard.entranceExam
185
186 @property
187 def loggedIn(self):
188 """Indicate if the user has logged in properly."""
189 return self._wizard.loggedIn
190
191 @property
192 def loginResult(self):
193 """Get the result of the login."""
194 return self._wizard.loginResult
195
196 @property
197 def bookedFlight(self):
198 """Get the booked flight selected, if any."""
199 return self._wizard.bookedFlight
200
201 @property
202 def cargoWeight(self):
203 """Get the cargo weight."""
204 return self._wizard.cargoWeight
205
206 @property
207 def zfw(self):
208 """Get Zero-Fuel Weight calculated for the current flight."""
209 return self._wizard.zfw
210
211 @property
212 def filedCruiseAltitude(self):
213 """Get cruise altitude filed for the current flight."""
214 return self._wizard.filedCruiseAltitude
215
216 @property
217 def cruiseAltitude(self):
218 """Get cruise altitude set for the current flight."""
219 return self._wizard.cruiseAltitude
220
221 @property
222 def route(self):
223 """Get the flight route."""
224 return self._wizard.route
225
226 @property
227 def departureMETAR(self):
228 """Get the METAR of the deprature airport."""
229 return self._wizard.departureMETAR
230
231 @property
232 def arrivalMETAR(self):
233 """Get the METAR of the deprature airport."""
234 return self._wizard.arrivalMETAR
235
236 @property
237 def departureRunway(self):
238 """Get the name of the departure runway."""
239 return self._wizard.departureRunway
240
241 @property
242 def sid(self):
243 """Get the SID."""
244 return self._wizard.sid
245
246 @property
247 def v1(self):
248 """Get the V1 speed calculated for the flight."""
249 return self._wizard.v1
250
251 @property
252 def vr(self):
253 """Get the Vr speed calculated for the flight."""
254 return self._wizard.vr
255
256 @property
257 def v2(self):
258 """Get the V2 speed calculated for the flight."""
259 return self._wizard.v2
260
261 @property
262 def arrivalRunway(self):
263 """Get the arrival runway."""
264 return self._wizard.arrivalRunway
265
266 @property
267 def star(self):
268 """Get the STAR."""
269 return self._wizard.star
270
271 @property
272 def transition(self):
273 """Get the transition."""
274 return self._wizard.transition
275
276 @property
277 def approachType(self):
278 """Get the approach type."""
279 return self._wizard.approachType
280
281 @property
282 def vref(self):
283 """Get the Vref speed calculated for the flight."""
284 return self._wizard.vref
285
286 @property
287 def flightType(self):
288 """Get the flight type."""
289 return self._wizard.flightType
290
291 @property
292 def online(self):
293 """Get whether the flight was online or not."""
294 return self._wizard.online
295
296 @property
297 def comments(self):
298 """Get the comments."""
299 return self._flightInfo.comments
300
301 @property
302 def flightDefects(self):
303 """Get the flight defects."""
304 return self._flightInfo.flightDefects
305
306 @property
307 def delayCodes(self):
308 """Get the delay codes."""
309 return self._flightInfo.delayCodes
310
311 def run(self):
312 """Run the GUI."""
313 if self.config.autoUpdate:
314 self._updater = Updater(self,
315 self._programDirectory,
316 self.config.updateURL,
317 self._mainWindow)
318 self._updater.start()
319
320 singleton.raiseCallback = self.raiseCallback
321 gtk.main()
322 singleton.raiseCallback = None
323
324 self._disconnect()
325
326 def connected(self, fsType, descriptor):
327 """Called when we have connected to the simulator."""
328 self._connected = True
329 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
330 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
331 "Welcome to MAVA Logger X " + const.VERSION)
332 gobject.idle_add(self._handleConnected, fsType, descriptor)
333
334 def _handleConnected(self, fsType, descriptor):
335 """Called when the connection to the simulator has succeeded."""
336 self._statusbar.updateConnection(self._connecting, self._connected)
337 self.endBusy()
338 if not self._reconnecting:
339 self._wizard.connected(fsType, descriptor)
340 self._reconnecting = False
341 self._listenHotkeys()
342
343 def connectionFailed(self):
344 """Called when the connection failed."""
345 self._logger.untimedMessage("Connection to the simulator failed")
346 gobject.idle_add(self._connectionFailed)
347
348 def _connectionFailed(self):
349 """Called when the connection failed."""
350 self.endBusy()
351 self._statusbar.updateConnection(self._connecting, self._connected)
352
353 dialog = gtk.MessageDialog(parent = self._mainWindow,
354 type = MESSAGETYPE_ERROR,
355 message_format = xstr("conn_failed"))
356
357 dialog.set_title(WINDOW_TITLE_BASE)
358 dialog.format_secondary_markup(xstr("conn_failed_sec"))
359
360 dialog.add_button(xstr("button_cancel"), 0)
361 dialog.add_button(xstr("button_tryagain"), 1)
362 dialog.set_default_response(1)
363
364 result = dialog.run()
365 dialog.hide()
366 if result == 1:
367 self.beginBusy(xstr("connect_busy"))
368 self._simulator.reconnect()
369 else:
370 self.reset()
371
372 def disconnected(self):
373 """Called when we have disconnected from the simulator."""
374 self._connected = False
375 self._logger.untimedMessage("Disconnected from the simulator")
376
377 gobject.idle_add(self._disconnected)
378
379 def _disconnected(self):
380 """Called when we have disconnected from the simulator unexpectedly."""
381 self._statusbar.updateConnection(self._connecting, self._connected)
382
383 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
384 message_format = xstr("conn_broken"),
385 parent = self._mainWindow)
386 dialog.set_title(WINDOW_TITLE_BASE)
387 dialog.format_secondary_markup(xstr("conn_broken_sec"))
388
389 dialog.add_button(xstr("button_cancel"), 0)
390 dialog.add_button(xstr("button_reconnect"), 1)
391 dialog.set_default_response(1)
392
393 result = dialog.run()
394 dialog.hide()
395 if result == 1:
396 self.beginBusy(xstr("connect_busy"))
397 self._reconnecting = True
398 self._simulator.reconnect()
399 else:
400 self.reset()
401
402 def enableFlightInfo(self):
403 """Enable the flight info tab."""
404 self._flightInfo.enable()
405
406 def cancelFlight(self):
407 """Cancel the current file, if the user confirms it."""
408 dialog = gtk.MessageDialog(parent = self._mainWindow,
409 type = MESSAGETYPE_QUESTION,
410 message_format = xstr("cancelFlight_question"))
411
412 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
413 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
414
415 dialog.set_title(WINDOW_TITLE_BASE)
416 result = dialog.run()
417 dialog.hide()
418
419 if result==RESPONSETYPE_YES:
420 self.reset()
421
422 def reset(self):
423 """Reset the GUI."""
424 self._disconnect()
425
426 self._flightInfo.reset()
427 self._flightInfo.disable()
428 self.resetFlightStatus()
429
430 self._weightHelp.reset()
431 self._weightHelp.disable()
432 self._notebook.set_current_page(0)
433
434 self._logView.get_buffer().set_text("")
435
436 if self.loggedIn:
437 self._wizard.reloadFlights(self._handleReloadResult)
438 else:
439 self._wizard.reset(None)
440
441 def _handleReloadResult(self, returned, result):
442 """Handle the result of the reloading of the flights."""
443 self._wizard.reset(result if returned and result.loggedIn else None)
444
445 def _disconnect(self, closingMessage = None, duration = 3):
446 """Disconnect from the simulator if connected."""
447 self.stopMonitoring()
448 self._clearHotkeys()
449
450 if self._connected:
451 if closingMessage is None:
452 self._flight.simulator.disconnect()
453 else:
454 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
455 closingMessage, duration,
456 disconnect = True)
457 self._connected = False
458
459 self._connecting = False
460 self._reconnecting = False
461 self._statusbar.updateConnection(False, False)
462 self._weightHelp.disable()
463
464 return True
465
466 def addFlightLogLine(self, timeStr, line, isFault = False):
467 """Write the given message line to the log."""
468 gobject.idle_add(self._writeLog,
469 formatFlightLogLine(timeStr, line),
470 self._logView, isFault)
471
472 def updateFlightLogLine(self, index, timeStr, line):
473 """Update the line with the given index."""
474 gobject.idle_add(self._updateFlightLogLine, index,
475 formatFlightLogLine(timeStr, line))
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, isFault = False):
917 """Write the given message to the log."""
918 buffer = logView.get_buffer()
919 appendTextBuffer(buffer, msg, isFault = isFault)
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.