source: src/mlx/gui/gui.py@ 830:d0e4741cda76

Last change on this file since 830:d0e4741cda76 was 830:d0e4741cda76, checked in by István Váradi <ivaradi@…>, 7 years ago

A PIREP for a pending flight can be viewed (re #307)

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