source: src/mlx/gui/gui.py@ 625:363e7cf53546

Last change on this file since 625:363e7cf53546 was 620:bcbc2bc37909, checked in by István Váradi <ivaradi@…>, 10 years ago

The border of the fault explanation widget is highlighted if there are any unexplained faults (re #257)

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