source: src/mlx/gui/gui.py@ 609:4eac92648123

Last change on this file since 609:4eac92648123 was 609:4eac92648123, checked in by István Váradi <ivaradi@…>, 9 years ago

Disconnection from the simulator is logged (re #249)

File size: 57.6 KB
Line 
1# -*- coding: utf-8 -*-
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.callouts import ApproachCalloutsEditor
15from mlx.gui.pirep import PIREPViewer
16from mlx.gui.bugreport import BugReportDialog
17
18import mlx.const as const
19import mlx.fs as fs
20import mlx.flight as flight
21import mlx.logger as logger
22import mlx.acft as acft
23import mlx.web as web
24import mlx.singleton as singleton
25import mlx.airports as airports
26from mlx.i18n import xstr, getLanguage
27from mlx.pirep import PIREP
28
29import time
30import threading
31import sys
32import datetime
33import webbrowser
34
35#------------------------------------------------------------------------------
36
37## @package mlx.gui.gui
38#
39# The main GUI class.
40#
41# The \ref GUI class is the main class of the GUI. It is a connection listener,
42# and aggregates all the windows, the menu, etc. It maintains the connection to
43# the simulator as well as the flight object.
44
45#------------------------------------------------------------------------------
46
47class GUI(fs.ConnectionListener):
48 """The main GUI class."""
49 _authors = [ (u"Váradi", u"István", "prog_test"),
50 (u"Galyassy", u"Tamás", "negotiation"),
51 (u"Kurják", u"Ákos", "test"),
52 (u"Nagy", u"Dániel", "test"),
53 (u"Radó", u"Iván", "test"),
54 (u"Petrovszki", u"Gábor", "test"),
55 (u"Serfőző", u"Tamás", "test"),
56 (u"Szebenyi", u"Bálint", "test"),
57 (u"Zsebényi-Loksa", u"Gergely", "test") ]
58
59 def __init__(self, programDirectory, config):
60 """Construct the GUI."""
61 gobject.threads_init()
62
63 self._programDirectory = programDirectory
64 self.config = config
65 self._connecting = False
66 self._reconnecting = False
67 self._connected = False
68 self._logger = logger.Logger(self)
69 self._flight = None
70 self._simulator = None
71 self._fsType = None
72 self._monitoring = False
73
74 self._fleet = None
75
76 self._fleetCallback = None
77
78 self._updatePlaneCallback = None
79 self._updatePlaneTailNumber = None
80 self._updatePlaneStatus = None
81 self._updatePlaneGateNumber = None
82
83 self._stdioLock = threading.Lock()
84 self._stdioText = ""
85 self._stdioStartingLine = True
86
87 self._sendPIREPCallback = None
88 self._sendBugReportCallback = None
89
90 self.webHandler = web.Handler()
91 self.webHandler.start()
92
93 self.toRestart = False
94
95 def build(self, iconDirectory):
96 """Build the GUI."""
97
98 self._mainWindow = window = gtk.Window()
99 window.set_title(WINDOW_TITLE_BASE)
100 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
101 window.set_resizable(False)
102 window.connect("delete-event", self.deleteMainWindow)
103 window.connect("window-state-event", self._handleMainWindowState)
104 accelGroup = gtk.AccelGroup()
105 window.add_accel_group(accelGroup)
106
107 mainVBox = gtk.VBox()
108 window.add(mainVBox)
109
110 self._preferences = Preferences(self)
111 self._checklistEditor = ChecklistEditor(self)
112 self._approachCalloutsEditor = ApproachCalloutsEditor(self)
113 self._bugReportDialog = BugReportDialog(self)
114
115 menuBar = self._buildMenuBar(accelGroup)
116 mainVBox.pack_start(menuBar, False, False, 0)
117
118 self._notebook = gtk.Notebook()
119 mainVBox.pack_start(self._notebook, True, True, 4)
120
121 self._wizard = Wizard(self)
122 label = gtk.Label(xstr("tab_flight"))
123 label.set_use_underline(True)
124 label.set_tooltip_text(xstr("tab_flight_tooltip"))
125 self._notebook.append_page(self._wizard, label)
126
127 self._flightInfo = FlightInfo(self)
128 label = gtk.Label(xstr("tab_flight_info"))
129 label.set_use_underline(True)
130 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
131 self._notebook.append_page(self._flightInfo, label)
132 self._flightInfo.disable()
133
134 self._weightHelp = WeightHelp(self)
135 label = gtk.Label(xstr("tab_weight_help"))
136 label.set_use_underline(True)
137 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
138 self._notebook.append_page(self._weightHelp, label)
139
140 (logWidget, self._logView) = self._buildLogWidget()
141 addFaultTag(self._logView.get_buffer())
142 label = gtk.Label(xstr("tab_log"))
143 label.set_use_underline(True)
144 label.set_tooltip_text(xstr("tab_log_tooltip"))
145 self._notebook.append_page(logWidget, label)
146
147 self._fleetGateStatus = FleetGateStatus(self)
148 label = gtk.Label(xstr("tab_gates"))
149 label.set_use_underline(True)
150 label.set_tooltip_text(xstr("tab_gates_tooltip"))
151 self._notebook.append_page(self._fleetGateStatus, label)
152
153 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
154 self._debugLogWidget.show_all()
155
156 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
157
158 self._statusbar = Statusbar(iconDirectory)
159 mainVBox.pack_start(self._statusbar, False, False, 0)
160
161 self._notebook.connect("switch-page", self._notebookPageSwitch)
162
163 self._monitorWindow = MonitorWindow(self, iconDirectory)
164 self._monitorWindow.add_accel_group(accelGroup)
165 self._monitorWindowX = None
166 self._monitorWindowY = None
167 self._selfToggling = False
168
169 self._pirepViewer = PIREPViewer(self)
170
171 window.show_all()
172 self._wizard.grabDefault()
173 self._weightHelp.reset()
174 self._weightHelp.disable()
175
176 self._statusIcon = StatusIcon(iconDirectory, self)
177
178 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
179 else gdk.WATCH)
180
181 self._loadPIREPDialog = None
182 self._lastLoadedPIREP = None
183
184 self._hotkeySetID = None
185 self._pilotHotkeyIndex = None
186 self._checklistHotkeyIndex = None
187
188 self._aboutDialog = None
189
190 @property
191 def mainWindow(self):
192 """Get the main window of the GUI."""
193 return self._mainWindow
194
195 @property
196 def logger(self):
197 """Get the logger used by us."""
198 return self._logger
199
200 @property
201 def simulator(self):
202 """Get the simulator used by us."""
203 return self._simulator
204
205 @property
206 def flight(self):
207 """Get the flight being performed."""
208 return self._flight
209
210 @property
211 def fsType(self):
212 """Get the flight simulator type."""
213 return self._fsType
214
215 @property
216 def entranceExam(self):
217 """Get whether an entrance exam is about to be taken."""
218 return self._wizard.entranceExam
219
220 @property
221 def loggedIn(self):
222 """Indicate if the user has logged in properly."""
223 return self._wizard.loggedIn
224
225 @property
226 def loginResult(self):
227 """Get the result of the login."""
228 return self._wizard.loginResult
229
230 @property
231 def bookedFlight(self):
232 """Get the booked flight selected, if any."""
233 return self._wizard.bookedFlight
234
235 @property
236 def numCrew(self):
237 """Get the number of crew members."""
238 return self._wizard.numCrew
239
240 @property
241 def numPassengers(self):
242 """Get the number of passengers."""
243 return self._wizard.numPassengers
244
245 @property
246 def bagWeight(self):
247 """Get the bag weight."""
248 return self._wizard.bagWeight
249
250 @property
251 def cargoWeight(self):
252 """Get the cargo weight."""
253 return self._wizard.cargoWeight
254
255 @property
256 def mailWeight(self):
257 """Get the mail weight."""
258 return self._wizard.mailWeight
259
260 @property
261 def zfw(self):
262 """Get Zero-Fuel Weight calculated for the current flight."""
263 return self._wizard.zfw
264
265 @property
266 def filedCruiseAltitude(self):
267 """Get cruise altitude filed for the current flight."""
268 return self._wizard.filedCruiseAltitude
269
270 @property
271 def cruiseAltitude(self):
272 """Get cruise altitude set for the current flight."""
273 return self._wizard.cruiseAltitude
274
275 @property
276 def loggableCruiseAltitude(self):
277 """Get the cruise altitude that can be logged."""
278 return self._wizard.loggableCruiseAltitude
279
280 @property
281 def route(self):
282 """Get the flight route."""
283 return self._wizard.route
284
285 @property
286 def departureMETAR(self):
287 """Get the METAR of the deprature airport."""
288 return self._wizard.departureMETAR
289
290 @property
291 def arrivalMETAR(self):
292 """Get the METAR of the deprature airport."""
293 return self._wizard.arrivalMETAR
294
295 @property
296 def departureRunway(self):
297 """Get the name of the departure runway."""
298 return self._wizard.departureRunway
299
300 @property
301 def sid(self):
302 """Get the SID."""
303 return self._wizard.sid
304
305 @property
306 def v1(self):
307 """Get the V1 speed calculated for the flight."""
308 return self._wizard.v1
309
310 @property
311 def vr(self):
312 """Get the Vr speed calculated for the flight."""
313 return self._wizard.vr
314
315 @property
316 def v2(self):
317 """Get the V2 speed calculated for the flight."""
318 return self._wizard.v2
319
320 @property
321 def derate(self):
322 """Get the derate value calculated for the flight."""
323 return self._wizard.derate
324
325 @property
326 def takeoffAntiIceOn(self):
327 """Get whether the anti-ice system was on during take-off."""
328 return self._wizard.takeoffAntiIceOn
329
330 @takeoffAntiIceOn.setter
331 def takeoffAntiIceOn(self, value):
332 """Set the anti-ice on indicator."""
333 gobject.idle_add(self._setTakeoffAntiIceOn, value)
334
335 @property
336 def rtoIndicated(self):
337 """Get whether the pilot has indicated than an RTO has occured."""
338 return self._wizard.rtoIndicated
339
340 @property
341 def arrivalRunway(self):
342 """Get the arrival runway."""
343 return self._wizard.arrivalRunway
344
345 @property
346 def star(self):
347 """Get the STAR."""
348 return self._wizard.star
349
350 @property
351 def transition(self):
352 """Get the transition."""
353 return self._wizard.transition
354
355 @property
356 def approachType(self):
357 """Get the approach type."""
358 return self._wizard.approachType
359
360 @property
361 def vref(self):
362 """Get the Vref speed calculated for the flight."""
363 return self._wizard.vref
364
365 @property
366 def landingAntiIceOn(self):
367 """Get whether the anti-ice system was on during landing."""
368 return self._wizard.landingAntiIceOn
369
370 @landingAntiIceOn.setter
371 def landingAntiIceOn(self, value):
372 """Set the anti-ice on indicator."""
373 gobject.idle_add(self._setLandingAntiIceOn, value)
374
375 @property
376 def flightType(self):
377 """Get the flight type."""
378 return self._wizard.flightType
379
380 @property
381 def online(self):
382 """Get whether the flight was online or not."""
383 return self._wizard.online
384
385 @property
386 def comments(self):
387 """Get the comments."""
388 return self._flightInfo.comments
389
390 @property
391 def hasComments(self):
392 """Indicate whether there is a comment."""
393 return self._flightInfo.hasComments
394
395 @property
396 def flightDefects(self):
397 """Get the flight defects."""
398 return self._flightInfo.faultsAndExplanations
399
400 @property
401 def delayCodes(self):
402 """Get the delay codes."""
403 return self._flightInfo.delayCodes
404
405 @property
406 def hasDelayCode(self):
407 """Determine if there is at least one delay code selected."""
408 return self._flightInfo.hasDelayCode
409
410 @property
411 def faultsFullyExplained(self):
412 """Determine if all the faults have been fully explained by the
413 user."""
414 return self._flightInfo.faultsFullyExplained
415
416 def run(self):
417 """Run the GUI."""
418 if self.config.autoUpdate:
419 self._updater = Updater(self,
420 self._programDirectory,
421 self.config.updateURL,
422 self._mainWindow)
423 self._updater.start()
424
425 singleton.raiseCallback = self.raiseCallback
426 gtk.main()
427 singleton.raiseCallback = None
428
429 self._disconnect()
430
431 def connected(self, fsType, descriptor):
432 """Called when we have connected to the simulator."""
433 self._connected = True
434 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
435 (const.VERSION, descriptor))
436 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
437 "Welcome to MAVA Logger X " + const.VERSION)
438 gobject.idle_add(self._handleConnected, fsType, descriptor)
439
440 def _handleConnected(self, fsType, descriptor):
441 """Called when the connection to the simulator has succeeded."""
442 self._statusbar.updateConnection(self._connecting, self._connected)
443 self.endBusy()
444 if not self._reconnecting:
445 self._wizard.connected(fsType, descriptor)
446 self._reconnecting = False
447 self._fsType = fsType
448 self._listenHotkeys()
449
450 def connectionFailed(self):
451 """Called when the connection failed."""
452 self._logger.untimedMessage("Connection to the simulator failed")
453 gobject.idle_add(self._connectionFailed)
454
455 def _connectionFailed(self):
456 """Called when the connection failed."""
457 self.endBusy()
458 self._statusbar.updateConnection(self._connecting, self._connected)
459
460 dialog = gtk.MessageDialog(parent = self._mainWindow,
461 type = MESSAGETYPE_ERROR,
462 message_format = xstr("conn_failed"))
463
464 dialog.set_title(WINDOW_TITLE_BASE)
465 dialog.format_secondary_markup(xstr("conn_failed_sec"))
466
467 dialog.add_button(xstr("button_cancel"), 0)
468 dialog.add_button(xstr("button_tryagain"), 1)
469 dialog.set_default_response(1)
470
471 result = dialog.run()
472 dialog.hide()
473 if result == 1:
474 self.beginBusy(xstr("connect_busy"))
475 self._simulator.reconnect()
476 else:
477 self.reset()
478
479 def disconnected(self):
480 """Called when we have disconnected from the simulator."""
481 self._connected = False
482 self._logger.untimedMessage("Disconnected from the simulator")
483 if self._flight is not None:
484 self._flight.disconnected()
485
486 gobject.idle_add(self._disconnected)
487
488 def _disconnected(self):
489 """Called when we have disconnected from the simulator unexpectedly."""
490 self._statusbar.updateConnection(self._connecting, self._connected)
491
492 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
493 message_format = xstr("conn_broken"),
494 parent = self._mainWindow)
495 dialog.set_title(WINDOW_TITLE_BASE)
496 dialog.format_secondary_markup(xstr("conn_broken_sec"))
497
498 dialog.add_button(xstr("button_cancel"), 0)
499 dialog.add_button(xstr("button_reconnect"), 1)
500 dialog.set_default_response(1)
501
502 result = dialog.run()
503 dialog.hide()
504 if result == 1:
505 self.beginBusy(xstr("connect_busy"))
506 self._reconnecting = True
507 self._simulator.reconnect()
508 else:
509 self.reset()
510
511 def enableFlightInfo(self, aircraftType):
512 """Enable the flight info tab."""
513 self._flightInfo.enable(aircraftType)
514
515 def cancelFlight(self):
516 """Cancel the current file, if the user confirms it."""
517 dialog = gtk.MessageDialog(parent = self._mainWindow,
518 type = MESSAGETYPE_QUESTION,
519 message_format = xstr("cancelFlight_question"))
520
521 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
522 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
523
524 dialog.set_title(WINDOW_TITLE_BASE)
525 result = dialog.run()
526 dialog.hide()
527
528 if result==RESPONSETYPE_YES:
529 self.reset()
530
531 def reset(self):
532 """Reset the GUI."""
533 self._disconnect()
534
535 self._simulator = None
536
537 self._flightInfo.reset()
538 self._flightInfo.disable()
539 self.resetFlightStatus()
540
541 self._weightHelp.reset()
542 self._weightHelp.disable()
543 self._notebook.set_current_page(0)
544
545 self._logView.get_buffer().set_text("")
546
547 if self.loggedIn:
548 self._wizard.reloadFlights(self._handleReloadResult)
549 else:
550 self._wizard.reset(None)
551
552 def _handleReloadResult(self, returned, result):
553 """Handle the result of the reloading of the flights."""
554 self._wizard.reset(result if returned and result.loggedIn else None)
555
556 def _disconnect(self, closingMessage = None, duration = 3):
557 """Disconnect from the simulator if connected."""
558 self.stopMonitoring()
559 self._clearHotkeys()
560
561 if self._connected:
562 if closingMessage is None:
563 self._flight.simulator.disconnect()
564 else:
565 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
566 closingMessage, duration,
567 disconnect = True)
568 self._connected = False
569
570 self._connecting = False
571 self._reconnecting = False
572 self._statusbar.updateConnection(False, False)
573 self._weightHelp.disable()
574
575 return True
576
577 def insertFlightLogLine(self, index, timestampString, text, isFault):
578 """Insert the flight log line with the given data."""
579 gobject.idle_add(self._insertFlightLogLine, index,
580 formatFlightLogLine(timestampString, text),
581 isFault)
582
583 def _insertFlightLogLine(self, index, line, isFault):
584 """Perform the real insertion.
585
586 To be called from the event loop."""
587 buffer = self._logView.get_buffer()
588 lineIter = buffer.get_iter_at_line(index)
589 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
590 self._logView.scroll_mark_onscreen(buffer.get_insert())
591
592 def removeFlightLogLine(self, index):
593 """Remove the flight log line with the given index."""
594 gobject.idle_add(self._removeFlightLogLine, index)
595
596 def addFault(self, id, timestampString, text):
597 """Add a fault to the list of faults."""
598 faultText = formatFlightLogLine(timestampString, text).strip()
599 gobject.idle_add(self._flightInfo.addFault, id, faultText)
600
601 def updateFault(self, id, timestampString, text):
602 """Update a fault in the list of faults."""
603 faultText = formatFlightLogLine(timestampString, text).strip()
604 gobject.idle_add(self._flightInfo.updateFault, id, faultText)
605
606 def clearFault(self, id):
607 """Clear a fault in the list of faults."""
608 gobject.idle_add(self._flightInfo.clearFault, id)
609
610 def _removeFlightLogLine(self, index):
611 """Perform the real removal."""
612 buffer = self._logView.get_buffer()
613 startIter = buffer.get_iter_at_line(index)
614 endIter = buffer.get_iter_at_line(index+1)
615 buffer.delete(startIter, endIter)
616 self._logView.scroll_mark_onscreen(buffer.get_insert())
617
618 def check(self, flight, aircraft, logger, oldState, state):
619 """Update the data."""
620 gobject.idle_add(self._monitorWindow.setData, state)
621 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
622
623 def resetFlightStatus(self):
624 """Reset the status of the flight."""
625 self._statusbar.resetFlightStatus()
626 self._statusbar.updateTime()
627 self._statusIcon.resetFlightStatus()
628
629 def setStage(self, stage):
630 """Set the stage of the flight."""
631 gobject.idle_add(self._setStage, stage)
632
633 def _setStage(self, stage):
634 """Set the stage of the flight."""
635 self._statusbar.setStage(stage)
636 self._statusIcon.setStage(stage)
637 self._wizard.setStage(stage)
638 if stage==const.STAGE_END:
639 welcomeMessage = \
640 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
641 self._disconnect(closingMessage =
642 "Flight plan closed. " + welcomeMessage,
643 duration = 5)
644
645 def setRating(self, rating):
646 """Set the rating of the flight."""
647 gobject.idle_add(self._setRating, rating)
648
649 def _setRating(self, rating):
650 """Set the rating of the flight."""
651 self._statusbar.setRating(rating)
652 self._statusIcon.setRating(rating)
653
654 def setNoGo(self, reason):
655 """Set the rating of the flight to No-Go with the given reason."""
656 gobject.idle_add(self._setNoGo, reason)
657
658 def _setNoGo(self, reason):
659 """Set the rating of the flight."""
660 self._statusbar.setNoGo(reason)
661 self._statusIcon.setNoGo(reason)
662
663 def _handleMainWindowState(self, window, event):
664 """Hande a change in the state of the window"""
665 iconified = gdk.WindowState.ICONIFIED if pygobject \
666 else gdk.WINDOW_STATE_ICONIFIED
667
668 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
669 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
670 self._statusIcon.mainWindowHidden()
671 else:
672 self._statusIcon.mainWindowShown()
673
674 if self.config.hideMinimizedWindow and not pygobject and \
675 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
676 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
677 self.hideMainWindow(savePosition = False)
678 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
679 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
680 self._mainWindow.present()
681
682 def raiseCallback(self):
683 """Callback for the singleton handling code."""
684 gobject.idle_add(self.raiseMainWindow)
685
686 def raiseMainWindow(self):
687 """Show the main window if invisible, and raise it."""
688 if not self._mainWindow.get_visible():
689 self.showMainWindow()
690 self._mainWindow.present()
691
692 def deleteMainWindow(self, window, event):
693 """Handle the delete event for the main window."""
694 if self.config.quitOnClose:
695 self._quit()
696 else:
697 self.hideMainWindow()
698 return True
699
700 def hideMainWindow(self, savePosition = True):
701 """Hide the main window and save its position."""
702 if savePosition:
703 (self._mainWindowX, self._mainWindowY) = \
704 self._mainWindow.get_window().get_root_origin()
705 else:
706 self._mainWindowX = self._mainWindowY = None
707 self._mainWindow.hide()
708 return True
709
710 def showMainWindow(self):
711 """Show the main window at its former position."""
712 if self._mainWindowX is not None and self._mainWindowY is not None:
713 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
714
715 if pygobject:
716 self._mainWindow.show()
717 else:
718 self._mainWindow.present()
719 self._mainWindow.deiconify()
720
721 def toggleMainWindow(self):
722 """Toggle the main window."""
723 if self._mainWindow.get_visible():
724 self.hideMainWindow()
725 else:
726 self.showMainWindow()
727
728 def hideMonitorWindow(self, savePosition = True):
729 """Hide the monitor window."""
730 if savePosition:
731 (self._monitorWindowX, self._monitorWindowY) = \
732 self._monitorWindow.get_window().get_root_origin()
733 else:
734 self._monitorWindowX = self._monitorWindowY = None
735 self._monitorWindow.hide()
736 self._statusIcon.monitorWindowHidden()
737 if self._showMonitorMenuItem.get_active():
738 self._selfToggling = True
739 self._showMonitorMenuItem.set_active(False)
740 return True
741
742 def showMonitorWindow(self):
743 """Show the monitor window."""
744 if self._monitorWindowX is not None and self._monitorWindowY is not None:
745 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
746 self._monitorWindow.show_all()
747 self._statusIcon.monitorWindowShown()
748 if not self._showMonitorMenuItem.get_active():
749 self._selfToggling = True
750 self._showMonitorMenuItem.set_active(True)
751
752 def _toggleMonitorWindow(self, menuItem):
753 if self._selfToggling:
754 self._selfToggling = False
755 elif self._monitorWindow.get_visible():
756 self.hideMonitorWindow()
757 else:
758 self.showMonitorWindow()
759
760 def restart(self):
761 """Quit and restart the application."""
762 self.toRestart = True
763 self._quit(force = True)
764
765 def flushStdIO(self):
766 """Flush any text to the standard error that could not be logged."""
767 if self._stdioText:
768 sys.__stderr__.write(self._stdioText)
769
770 def writeStdIO(self, text):
771 """Write the given text into standard I/O log."""
772 with self._stdioLock:
773 self._stdioText += text
774
775 gobject.idle_add(self._writeStdIO)
776
777 def beginBusy(self, message):
778 """Begin a period of background processing."""
779 self._wizard.set_sensitive(False)
780 self._weightHelp.set_sensitive(False)
781 self._mainWindow.get_window().set_cursor(self._busyCursor)
782 self._statusbar.updateBusyState(message)
783
784 def endBusy(self):
785 """End a period of background processing."""
786 self._mainWindow.get_window().set_cursor(None)
787 self._weightHelp.set_sensitive(True)
788 self._wizard.set_sensitive(True)
789 self._statusbar.updateBusyState(None)
790
791 def initializeWeightHelp(self):
792 """Initialize the weight help tab."""
793 self._weightHelp.reset()
794 self._weightHelp.enable()
795
796 def getFleetAsync(self, callback = None, force = None):
797 """Get the fleet asynchronously."""
798 gobject.idle_add(self.getFleet, callback, force)
799
800 def getFleet(self, callback = None, force = False):
801 """Get the fleet.
802
803 If force is False, and we already have a fleet retrieved,
804 that one will be used."""
805 if self._fleet is None or force:
806 self._fleetCallback = callback
807 self.beginBusy(xstr("fleet_busy"))
808 self.webHandler.getFleet(self._fleetResultCallback)
809 else:
810 callback(self._fleet)
811
812 def commentsChanged(self):
813 """Indicate that the comments have changed."""
814 self._wizard.commentsChanged()
815
816 def delayCodesChanged(self):
817 """Called when the delay codes have changed."""
818 self._wizard.delayCodesChanged()
819
820 def faultExplanationsChanged(self):
821 """Called when the status of the explanations of the faults have
822 changed."""
823 self._wizard.faultExplanationsChanged()
824
825 def updateRTO(self, inLoop = False):
826 """Indicate that the RTO state should be updated."""
827 if inLoop:
828 self._wizard.updateRTO()
829 else:
830 gobject.idle_add(self.updateRTO, True)
831
832 def rtoToggled(self, indicated):
833 """Called when the user has toggled the RTO checkbox."""
834 self._flight.rtoToggled(indicated)
835
836 def _fleetResultCallback(self, returned, result):
837 """Called when the fleet has been queried."""
838 gobject.idle_add(self._handleFleetResult, returned, result)
839
840 def _handleFleetResult(self, returned, result):
841 """Handle the fleet result."""
842 self.endBusy()
843 if returned:
844 self._fleet = result.fleet
845 else:
846 self._fleet = None
847
848 dialog = gtk.MessageDialog(parent = self.mainWindow,
849 type = MESSAGETYPE_ERROR,
850 message_format = xstr("fleet_failed"))
851 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
852 dialog.set_title(WINDOW_TITLE_BASE)
853 dialog.run()
854 dialog.hide()
855
856 callback = self._fleetCallback
857 self._fleetCallback = None
858 if callback is not None:
859 callback(self._fleet)
860 self._fleetGateStatus.handleFleet(self._fleet)
861
862 def updatePlane(self, tailNumber, status,
863 gateNumber = None, callback = None):
864 """Update the status of the given plane."""
865 self.beginBusy(xstr("fleet_update_busy"))
866
867 self._updatePlaneCallback = callback
868
869 self._updatePlaneTailNumber = tailNumber
870 self._updatePlaneStatus = status
871 self._updatePlaneGateNumber = gateNumber
872
873 self.webHandler.updatePlane(self._updatePlaneResultCallback,
874 tailNumber, status, gateNumber)
875
876 def _updatePlaneResultCallback(self, returned, result):
877 """Called when the status of a plane has been updated."""
878 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
879
880 def _handleUpdatePlaneResult(self, returned, result):
881 """Handle the plane update result."""
882 self.endBusy()
883 if returned:
884 success = result.success
885 if success:
886 if self._fleet is not None:
887 self._fleet.updatePlane(self._updatePlaneTailNumber,
888 self._updatePlaneStatus,
889 self._updatePlaneGateNumber)
890 self._fleetGateStatus.handleFleet(self._fleet)
891 else:
892 dialog = gtk.MessageDialog(parent = self.mainWindow,
893 type = MESSAGETYPE_ERROR,
894 message_format = xstr("fleet_update_failed"))
895 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
896 dialog.set_title(WINDOW_TITLE_BASE)
897 dialog.run()
898 dialog.hide()
899
900 success = None
901
902 callback = self._updatePlaneCallback
903 self._updatePlaneCallback = None
904 if callback is not None:
905 callback(success)
906
907 def _writeStdIO(self):
908 """Perform the real writing."""
909 with self._stdioLock:
910 text = self._stdioText
911 self._stdioText = ""
912 if not text: return
913
914 lines = text.splitlines()
915 if text[-1]=="\n":
916 text = ""
917 else:
918 text = lines[-1]
919 lines = lines[:-1]
920
921 now = datetime.datetime.now()
922 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
923
924 for line in lines:
925 #print >> sys.__stdout__, line
926 if self._stdioStartingLine:
927 self._writeLog(timeStr, self._debugLogView)
928 self._writeLog(line + "\n", self._debugLogView)
929 self._stdioStartingLine = True
930
931 if text:
932 #print >> sys.__stdout__, text,
933 if self._stdioStartingLine:
934 self._writeLog(timeStr, self._debugLogView)
935 self._writeLog(text, self._debugLogView)
936 self._stdioStartingLine = False
937
938 def connectSimulator(self, aircraftType, simulatorType):
939 """Connect to the simulator for the first time."""
940 self._logger.reset()
941
942 self._flight = flight.Flight(self._logger, self)
943 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
944 self._flight.aircraftType = aircraftType
945 self._flight.aircraft = acft.Aircraft.create(self._flight)
946 self._flight.aircraft._checkers.append(self)
947
948 if self._simulator is None:
949 self._simulator = fs.createSimulator(simulatorType, self)
950 fs.setupMessageSending(self.config, self._simulator)
951 self._setupTimeSync()
952
953 self._flight.simulator = self._simulator
954
955 self.beginBusy(xstr("connect_busy"))
956 self._statusbar.updateConnection(self._connecting, self._connected)
957
958 self._connecting = True
959 self._simulator.connect(self._flight.aircraft)
960
961 def startMonitoring(self):
962 """Start monitoring."""
963 if not self._monitoring:
964 self.simulator.startMonitoring()
965 self._monitoring = True
966
967 def stopMonitoring(self):
968 """Stop monitoring."""
969 if self._monitoring:
970 self.simulator.stopMonitoring()
971 self._monitoring = False
972
973 def cruiseLevelChanged(self):
974 """Called when the cruise level is changed in the flight wizard."""
975 if self._flight is not None:
976 return self._flight.cruiseLevelChanged()
977 else:
978 return False
979
980 def _buildMenuBar(self, accelGroup):
981 """Build the main menu bar."""
982 menuBar = gtk.MenuBar()
983
984 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
985 fileMenu = gtk.Menu()
986 fileMenuItem.set_submenu(fileMenu)
987 menuBar.append(fileMenuItem)
988
989 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
990 loadPIREPMenuItem.set_use_stock(True)
991 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
992 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
993 ord(xstr("menu_file_loadPIREP_key")),
994 CONTROL_MASK, ACCEL_VISIBLE)
995 loadPIREPMenuItem.connect("activate", self._loadPIREP)
996 fileMenu.append(loadPIREPMenuItem)
997
998 fileMenu.append(gtk.SeparatorMenuItem())
999
1000 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
1001 quitMenuItem.set_use_stock(True)
1002 quitMenuItem.set_label(xstr("menu_file_quit"))
1003 quitMenuItem.add_accelerator("activate", accelGroup,
1004 ord(xstr("menu_file_quit_key")),
1005 CONTROL_MASK, ACCEL_VISIBLE)
1006 quitMenuItem.connect("activate", self._quit)
1007 fileMenu.append(quitMenuItem)
1008
1009 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
1010 toolsMenu = gtk.Menu()
1011 toolsMenuItem.set_submenu(toolsMenu)
1012 menuBar.append(toolsMenuItem)
1013
1014 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
1015 checklistMenuItem.set_use_stock(True)
1016 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
1017 checklistMenuItem.add_accelerator("activate", accelGroup,
1018 ord(xstr("menu_tools_chklst_key")),
1019 CONTROL_MASK, ACCEL_VISIBLE)
1020 checklistMenuItem.connect("activate", self._editChecklist)
1021 toolsMenu.append(checklistMenuItem)
1022
1023 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
1024 approachCalloutsMenuItem.set_use_stock(True)
1025 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
1026 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
1027 ord(xstr("menu_tools_callouts_key")),
1028 CONTROL_MASK, ACCEL_VISIBLE)
1029 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
1030 toolsMenu.append(approachCalloutsMenuItem)
1031
1032 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
1033 prefsMenuItem.set_use_stock(True)
1034 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
1035 prefsMenuItem.add_accelerator("activate", accelGroup,
1036 ord(xstr("menu_tools_prefs_key")),
1037 CONTROL_MASK, ACCEL_VISIBLE)
1038 prefsMenuItem.connect("activate", self._editPreferences)
1039 toolsMenu.append(prefsMenuItem)
1040
1041 toolsMenu.append(gtk.SeparatorMenuItem())
1042
1043 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
1044 bugReportMenuItem.set_use_stock(True)
1045 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
1046 bugReportMenuItem.add_accelerator("activate", accelGroup,
1047 ord(xstr("menu_tools_bugreport_key")),
1048 CONTROL_MASK, ACCEL_VISIBLE)
1049 bugReportMenuItem.connect("activate", self._reportBug)
1050 toolsMenu.append(bugReportMenuItem)
1051
1052 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
1053 viewMenu = gtk.Menu()
1054 viewMenuItem.set_submenu(viewMenu)
1055 menuBar.append(viewMenuItem)
1056
1057 self._showMonitorMenuItem = gtk.CheckMenuItem()
1058 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
1059 self._showMonitorMenuItem.set_use_underline(True)
1060 self._showMonitorMenuItem.set_active(False)
1061 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
1062 ord(xstr("menu_view_monitor_key")),
1063 CONTROL_MASK, ACCEL_VISIBLE)
1064 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1065 viewMenu.append(self._showMonitorMenuItem)
1066
1067 showDebugMenuItem = gtk.CheckMenuItem()
1068 showDebugMenuItem.set_label(xstr("menu_view_debug"))
1069 showDebugMenuItem.set_use_underline(True)
1070 showDebugMenuItem.set_active(False)
1071 showDebugMenuItem.add_accelerator("activate", accelGroup,
1072 ord(xstr("menu_view_debug_key")),
1073 CONTROL_MASK, ACCEL_VISIBLE)
1074 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1075 viewMenu.append(showDebugMenuItem)
1076
1077 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1078 helpMenu = gtk.Menu()
1079 helpMenuItem.set_submenu(helpMenu)
1080 menuBar.append(helpMenuItem)
1081
1082 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1083 manualMenuItem.set_use_stock(True)
1084 manualMenuItem.set_label(xstr("menu_help_manual"))
1085 manualMenuItem.add_accelerator("activate", accelGroup,
1086 ord(xstr("menu_help_manual_key")),
1087 CONTROL_MASK, ACCEL_VISIBLE)
1088 manualMenuItem.connect("activate", self._showManual)
1089 helpMenu.append(manualMenuItem)
1090
1091 helpMenu.append(gtk.SeparatorMenuItem())
1092
1093 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1094 aboutMenuItem.set_use_stock(True)
1095 aboutMenuItem.set_label(xstr("menu_help_about"))
1096 aboutMenuItem.add_accelerator("activate", accelGroup,
1097 ord(xstr("menu_help_about_key")),
1098 CONTROL_MASK, ACCEL_VISIBLE)
1099 aboutMenuItem.connect("activate", self._showAbout)
1100 helpMenu.append(aboutMenuItem)
1101
1102 return menuBar
1103
1104 def _toggleDebugLog(self, menuItem):
1105 """Toggle the debug log."""
1106 if menuItem.get_active():
1107 label = gtk.Label(xstr("tab_debug_log"))
1108 label.set_use_underline(True)
1109 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
1110 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1111 self._notebook.set_current_page(self._debugLogPage)
1112 else:
1113 self._notebook.remove_page(self._debugLogPage)
1114
1115 def _buildLogWidget(self):
1116 """Build the widget for the log."""
1117 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1118
1119 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1120 padding_left = 16, padding_right = 16)
1121
1122 logScroller = gtk.ScrolledWindow()
1123 # FIXME: these should be constants in common
1124 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1125 else gtk.POLICY_AUTOMATIC,
1126 gtk.PolicyType.AUTOMATIC if pygobject
1127 else gtk.POLICY_AUTOMATIC)
1128 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1129 else gtk.SHADOW_IN)
1130 logView = gtk.TextView()
1131 logView.set_editable(False)
1132 logView.set_cursor_visible(False)
1133 logScroller.add(logView)
1134
1135 logBox = gtk.VBox()
1136 logBox.pack_start(logScroller, True, True, 0)
1137 logBox.set_size_request(-1, 200)
1138
1139 alignment.add(logBox)
1140
1141 return (alignment, logView)
1142
1143 def _writeLog(self, msg, logView, isFault = False):
1144 """Write the given message to the log."""
1145 buffer = logView.get_buffer()
1146 appendTextBuffer(buffer, msg, isFault = isFault)
1147 logView.scroll_mark_onscreen(buffer.get_insert())
1148
1149 def _quit(self, what = None, force = False):
1150 """Quit from the application."""
1151 if force:
1152 result=RESPONSETYPE_YES
1153 else:
1154 dialog = gtk.MessageDialog(parent = self._mainWindow,
1155 type = MESSAGETYPE_QUESTION,
1156 message_format = xstr("quit_question"))
1157
1158 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1159 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1160
1161 dialog.set_title(WINDOW_TITLE_BASE)
1162 result = dialog.run()
1163 dialog.hide()
1164
1165 if result==RESPONSETYPE_YES:
1166 self._statusIcon.destroy()
1167 return gtk.main_quit()
1168
1169 def _notebookPageSwitch(self, notebook, page, page_num):
1170 """Called when the current page of the notebook has changed."""
1171 if page_num==0:
1172 gobject.idle_add(self._wizard.grabDefault)
1173 else:
1174 self._mainWindow.set_default(None)
1175
1176 def isWizardActive(self):
1177 """Determine if the flight wizard is active."""
1178 return self._notebook.get_current_page()==0
1179
1180 def _editChecklist(self, menuItem):
1181 """Callback for editing the checklists."""
1182 self._checklistEditor.run()
1183
1184 def _editApproachCallouts(self, menuItem):
1185 """Callback for editing the approach callouts."""
1186 self._approachCalloutsEditor.run()
1187
1188 def _editPreferences(self, menuItem):
1189 """Callback for editing the preferences."""
1190 self._clearHotkeys()
1191 self._preferences.run(self.config)
1192 self._setupTimeSync()
1193 self._listenHotkeys()
1194
1195 def _reportBug(self, menuItem):
1196 """Callback for reporting a bug."""
1197 self._bugReportDialog.run()
1198
1199 def _setupTimeSync(self):
1200 """Enable or disable the simulator time synchronization based on the
1201 configuration."""
1202 simulator = self._simulator
1203 if simulator is not None:
1204 if self.config.syncFSTime:
1205 simulator.enableTimeSync()
1206 else:
1207 simulator.disableTimeSync()
1208
1209 def _loadPIREP(self, menuItem):
1210 """Load a PIREP for sending."""
1211 dialog = self._getLoadPirepDialog()
1212
1213 if self._lastLoadedPIREP:
1214 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1215 else:
1216 pirepDirectory = self.config.pirepDirectory
1217 if pirepDirectory is not None:
1218 dialog.set_current_folder(pirepDirectory)
1219
1220 result = dialog.run()
1221 dialog.hide()
1222
1223 if result==RESPONSETYPE_OK:
1224 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1225
1226 pirep = PIREP.load(self._lastLoadedPIREP)
1227 if pirep is None:
1228 dialog = gtk.MessageDialog(parent = self._mainWindow,
1229 type = MESSAGETYPE_ERROR,
1230 message_format = xstr("loadPIREP_failed"))
1231 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1232 dialog.set_title(WINDOW_TITLE_BASE)
1233 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1234 dialog.run()
1235 dialog.hide()
1236 else:
1237 dialog = self._getSendLoadedDialog(pirep)
1238 dialog.show_all()
1239 while True:
1240 result = dialog.run()
1241
1242 if result==RESPONSETYPE_OK:
1243 self.sendPIREP(pirep)
1244 elif result==1:
1245 self._pirepViewer.setPIREP(pirep)
1246 self._pirepViewer.show_all()
1247 self._pirepViewer.run()
1248 self._pirepViewer.hide()
1249 else:
1250 break
1251
1252 dialog.hide()
1253
1254 def _getLoadPirepDialog(self):
1255 """Get the PIREP loading file chooser dialog.
1256
1257 If it is not created yet, it will be created."""
1258 if self._loadPIREPDialog is None:
1259 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1260 xstr("loadPIREP_browser_title"),
1261 action = FILE_CHOOSER_ACTION_OPEN,
1262 buttons = (gtk.STOCK_CANCEL,
1263 RESPONSETYPE_CANCEL,
1264 gtk.STOCK_OK, RESPONSETYPE_OK),
1265 parent = self._mainWindow)
1266 dialog.set_modal(True)
1267
1268
1269 filter = gtk.FileFilter()
1270 filter.set_name(xstr("file_filter_pireps"))
1271 filter.add_pattern("*.pirep")
1272 dialog.add_filter(filter)
1273
1274 filter = gtk.FileFilter()
1275 filter.set_name(xstr("file_filter_all"))
1276 filter.add_pattern("*.*")
1277 dialog.add_filter(filter)
1278
1279 self._loadPIREPDialog = dialog
1280
1281 return self._loadPIREPDialog
1282
1283 def _getSendLoadedDialog(self, pirep):
1284 """Get a dialog displaying the main information of the flight from the
1285 PIREP and providing Cancel and Send buttons."""
1286 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1287 xstr("loadPIREP_send_title"),
1288 parent = self._mainWindow,
1289 flags = DIALOG_MODAL)
1290
1291 contentArea = dialog.get_content_area()
1292
1293 label = gtk.Label(xstr("loadPIREP_send_help"))
1294 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1295 xscale = 0.0, yscale = 0.0)
1296 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1297 padding_left = 48, padding_right = 48)
1298 alignment.add(label)
1299 contentArea.pack_start(alignment, False, False, 8)
1300
1301 table = gtk.Table(5, 2)
1302 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1303 xscale = 0.0, yscale = 0.0)
1304 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1305 padding_left = 48, padding_right = 48)
1306 table.set_row_spacings(4)
1307 table.set_col_spacings(16)
1308 tableAlignment.add(table)
1309 contentArea.pack_start(tableAlignment, True, True, 8)
1310
1311 bookedFlight = pirep.bookedFlight
1312
1313 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1314 label.set_use_markup(True)
1315 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1316 xscale = 0.0, yscale = 0.0)
1317 labelAlignment.add(label)
1318 table.attach(labelAlignment, 0, 1, 0, 1)
1319
1320 label = gtk.Label(bookedFlight.callsign)
1321 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1322 xscale = 0.0, yscale = 0.0)
1323 labelAlignment.add(label)
1324 table.attach(labelAlignment, 1, 2, 0, 1)
1325
1326 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1327 label.set_use_markup(True)
1328 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1329 xscale = 0.0, yscale = 0.0)
1330 labelAlignment.add(label)
1331 table.attach(labelAlignment, 0, 1, 1, 2)
1332
1333 label = gtk.Label(str(bookedFlight.departureTime.date()))
1334 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1335 xscale = 0.0, yscale = 0.0)
1336 labelAlignment.add(label)
1337 table.attach(labelAlignment, 1, 2, 1, 2)
1338
1339 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1340 label.set_use_markup(True)
1341 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1342 xscale = 0.0, yscale = 0.0)
1343 labelAlignment.add(label)
1344 table.attach(labelAlignment, 0, 1, 2, 3)
1345
1346 label = gtk.Label(bookedFlight.departureICAO)
1347 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1348 xscale = 0.0, yscale = 0.0)
1349 labelAlignment.add(label)
1350 table.attach(labelAlignment, 1, 2, 2, 3)
1351
1352 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1353 label.set_use_markup(True)
1354 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1355 xscale = 0.0, yscale = 0.0)
1356 labelAlignment.add(label)
1357 table.attach(labelAlignment, 0, 1, 3, 4)
1358
1359 label = gtk.Label(bookedFlight.arrivalICAO)
1360 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1361 xscale = 0.0, yscale = 0.0)
1362 labelAlignment.add(label)
1363 table.attach(labelAlignment, 1, 2, 3, 4)
1364
1365 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1366 label.set_use_markup(True)
1367 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1368 xscale = 0.0, yscale = 0.0)
1369 labelAlignment.add(label)
1370 table.attach(labelAlignment, 0, 1, 4, 5)
1371
1372 rating = pirep.rating
1373 label = gtk.Label()
1374 if rating<0:
1375 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1376 else:
1377 label.set_text("%.1f %%" % (rating,))
1378
1379 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1380 xscale = 0.0, yscale = 0.0)
1381 labelAlignment.add(label)
1382 table.attach(labelAlignment, 1, 2, 4, 5)
1383
1384 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1385 dialog.add_button(xstr("viewPIREP"), 1)
1386 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1387
1388 return dialog
1389
1390 def sendPIREP(self, pirep, callback = None):
1391 """Send the given PIREP."""
1392 self.beginBusy(xstr("sendPIREP_busy"))
1393 self._sendPIREPCallback = callback
1394 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1395
1396 def _pirepSentCallback(self, returned, result):
1397 """Callback for the PIREP sending result."""
1398 gobject.idle_add(self._handlePIREPSent, returned, result)
1399
1400 def _handlePIREPSent(self, returned, result):
1401 """Callback for the PIREP sending result."""
1402 self.endBusy()
1403 secondaryMarkup = None
1404 type = MESSAGETYPE_ERROR
1405 if returned:
1406 if result.success:
1407 type = MESSAGETYPE_INFO
1408 messageFormat = xstr("sendPIREP_success")
1409 secondaryMarkup = xstr("sendPIREP_success_sec")
1410 elif result.alreadyFlown:
1411 messageFormat = xstr("sendPIREP_already")
1412 secondaryMarkup = xstr("sendPIREP_already_sec")
1413 elif result.notAvailable:
1414 messageFormat = xstr("sendPIREP_notavail")
1415 else:
1416 messageFormat = xstr("sendPIREP_unknown")
1417 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1418 else:
1419 print "PIREP sending failed", result
1420 messageFormat = xstr("sendPIREP_failed")
1421 secondaryMarkup = xstr("sendPIREP_failed_sec")
1422
1423 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1424 type = type, message_format = messageFormat)
1425 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1426 dialog.set_title(WINDOW_TITLE_BASE)
1427 if secondaryMarkup is not None:
1428 dialog.format_secondary_markup(secondaryMarkup)
1429
1430 dialog.run()
1431 dialog.hide()
1432
1433 callback = self._sendPIREPCallback
1434 self._sendPIREPCallback = None
1435 if callback is not None:
1436 callback(returned, result)
1437
1438 def sendBugReport(self, summary, description, email, callback = None):
1439 """Send the bug report with the given data."""
1440 description += "\n\n" + ("=" * 40)
1441 description += "\n\nThe contents of the log:\n\n"
1442
1443 for (timestampString, text) in self._logger.lines:
1444 description += unicode(formatFlightLogLine(timestampString, text))
1445
1446 description += "\n\n" + ("=" * 40)
1447 description += "\n\nThe contents of the debug log:\n\n"
1448
1449 buffer = self._debugLogView.get_buffer()
1450 description += buffer.get_text(buffer.get_start_iter(),
1451 buffer.get_end_iter(), True)
1452
1453 self.beginBusy(xstr("sendBugReport_busy"))
1454 self._sendBugReportCallback = callback
1455 self.webHandler.sendBugReport(self._bugReportSentCallback,
1456 summary, description, email)
1457
1458 def _bugReportSentCallback(self, returned, result):
1459 """Callback function for the bug report sending result."""
1460 gobject.idle_add(self._handleBugReportSent, returned, result)
1461
1462 def _handleBugReportSent(self, returned, result):
1463 """Callback for the bug report sending result."""
1464 self.endBusy()
1465 secondaryMarkup = None
1466 type = MESSAGETYPE_ERROR
1467 if returned:
1468 if result.success:
1469 type = MESSAGETYPE_INFO
1470 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1471 secondaryMarkup = xstr("sendBugReport_success_sec")
1472 else:
1473 messageFormat = xstr("sendBugReport_error")
1474 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1475 else:
1476 messageFormat = xstr("sendBugReport_error")
1477 secondaryMarkup = xstr("sendBugReport_error_sec")
1478
1479 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1480 type = type, message_format = messageFormat)
1481 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1482 dialog.set_title(WINDOW_TITLE_BASE)
1483 if secondaryMarkup is not None:
1484 dialog.format_secondary_markup(secondaryMarkup)
1485
1486 dialog.run()
1487 dialog.hide()
1488
1489 callback = self._sendBugReportCallback
1490 self._sendBugReportCallback = None
1491 if callback is not None:
1492 callback(returned, result)
1493
1494 def _listenHotkeys(self):
1495 """Setup the hotkeys based on the configuration."""
1496 if self._hotkeySetID is None and self._simulator is not None:
1497 self._pilotHotkeyIndex = None
1498 self._checklistHotkeyIndex = None
1499
1500 hotkeys = []
1501
1502 config = self.config
1503 if config.enableSounds and config.pilotControlsSounds:
1504 self._pilotHotkeyIndex = len(hotkeys)
1505 hotkeys.append(config.pilotHotkey)
1506
1507 if config.enableChecklists:
1508 self._checklistHotkeyIndex = len(hotkeys)
1509 hotkeys.append(config.checklistHotkey)
1510
1511 if hotkeys:
1512 self._hotkeySetID = \
1513 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1514
1515 def _clearHotkeys(self):
1516 """Clear the hotkeys."""
1517 if self._hotkeySetID is not None:
1518 self._hotkeySetID=None
1519 self._simulator.clearHotkeys()
1520
1521 def _handleHotkeys(self, id, hotkeys):
1522 """Handle the hotkeys."""
1523 if id==self._hotkeySetID:
1524 for index in hotkeys:
1525 if index==self._pilotHotkeyIndex:
1526 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1527 self._flight.pilotHotkeyPressed()
1528 elif index==self._checklistHotkeyIndex:
1529 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1530 self._flight.checklistHotkeyPressed()
1531 else:
1532 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1533
1534 def _showManual(self, menuitem):
1535 """Show the user's manual."""
1536 webbrowser.open(url ="file://" +
1537 os.path.join(self._programDirectory, "doc", "manual",
1538 getLanguage(), "index.html"),
1539 new = 1)
1540
1541 def _showAbout(self, menuitem):
1542 """Show the about dialog."""
1543 dialog = self._getAboutDialog()
1544 dialog.show_all()
1545 dialog.run()
1546 dialog.hide()
1547
1548 def _getAboutDialog(self):
1549 """Get the about dialog.
1550
1551 If it does not exist yet, it will be created."""
1552 if self._aboutDialog is None:
1553 dialog = gtk.AboutDialog()
1554 dialog.set_transient_for(self._mainWindow)
1555 dialog.set_modal(True)
1556
1557 logoPath = os.path.join(self._programDirectory, "logo.png")
1558 logo = pixbuf_new_from_file(logoPath)
1559 dialog.set_logo(logo)
1560
1561 dialog.set_program_name(PROGRAM_NAME)
1562 dialog.set_version(const.VERSION)
1563 dialog.set_copyright("(c) 2012 by István Váradi")
1564 dialog.set_website("http://mlx.varadiistvan.hu")
1565 dialog.set_website_label(xstr("about_website"))
1566
1567 isHungarian = getLanguage()=="hu"
1568 authors = []
1569 for (familyName, firstName, role) in GUI._authors:
1570 author = "%s %s" % \
1571 (familyName if isHungarian else firstName,
1572 firstName if isHungarian else familyName)
1573 role = xstr("about_role_" + role)
1574 authors.append(author + " (" + role + ")")
1575 dialog.set_authors(authors)
1576
1577 dialog.set_license(xstr("about_license"))
1578
1579 if not pygobject:
1580 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1581
1582 self._aboutDialog = dialog
1583
1584 return self._aboutDialog
1585
1586 def _showAboutURL(self, dialog, link, user_data):
1587 """Show the about URL."""
1588 webbrowser.open(url = link, new = 1)
1589
1590 def _setTakeoffAntiIceOn(self, value):
1591 """Set the anti-ice on indicator."""
1592 self._wizard.takeoffAntiIceOn = value
1593
1594 def _setLandingAntiIceOn(self, value):
1595 """Set the anti-ice on indicator."""
1596 self._wizard.landingAntiIceOn = value
Note: See TracBrowser for help on using the repository browser.