source: src/mlx/gui/gui.py@ 219:c1d13fe9d52a

Last change on this file since 219:c1d13fe9d52a was 215:bff7327b2da0, checked in by István Váradi <ivaradi@…>, 12 years ago

Added support for flying without logging in

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