source: src/mlx/gui/gui.py@ 654:4ab835f44be6

cef
Last change on this file since 654:4ab835f44be6 was 652:692fece71a34, checked in by István Váradi <ivaradi@…>, 9 years ago

A new default update URL is set, and it cannot be changed *NBC* (re #272)

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