source: src/mlx/gui/gui.py@ 223:b12bd5ce792d

Last change on this file since 223:b12bd5ce792d was 221:2126271cea84, checked in by István Váradi <ivaradi@…>, 12 years ago

Implemented the PIREP viewer tabs for the comments and the log

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