source: src/mlx/gui/gui.py@ 211:fbd8dad0b6be

Last change on this file since 211:fbd8dad0b6be was 208:22ff615383e9, checked in by István Váradi <ivaradi@…>, 12 years ago

It is now possible to cancel a flight and to start a new one at the end and also to refresh the list of flights.

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