source: src/mlx/gui/gui.py@ 220:96ad81e11b85

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

The Data tab of the PIREP viewer works

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