source: src/mlx/gui/gui.py@ 834:5651e303daa9

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

Backed out changeset f300ebc45a7f

File size: 62.5 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.viewPIREP(pirep)
1303 else:
1304 break
1305
1306 dialog.hide()
1307
1308 def _getLoadPirepDialog(self):
1309 """Get the PIREP loading file chooser dialog.
1310
1311 If it is not created yet, it will be created."""
1312 if self._loadPIREPDialog is None:
1313 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1314 xstr("loadPIREP_browser_title"),
1315 action = FILE_CHOOSER_ACTION_OPEN,
1316 buttons = (gtk.STOCK_CANCEL,
1317 RESPONSETYPE_CANCEL,
1318 gtk.STOCK_OK, RESPONSETYPE_OK),
1319 parent = self._mainWindow)
1320 dialog.set_modal(True)
1321
1322
1323 filter = gtk.FileFilter()
1324 filter.set_name(xstr("file_filter_pireps"))
1325 filter.add_pattern("*.pirep")
1326 dialog.add_filter(filter)
1327
1328 filter = gtk.FileFilter()
1329 filter.set_name(xstr("file_filter_all"))
1330 filter.add_pattern("*.*")
1331 dialog.add_filter(filter)
1332
1333 self._loadPIREPDialog = dialog
1334
1335 return self._loadPIREPDialog
1336
1337 def _getSendLoadedDialog(self, pirep):
1338 """Get a dialog displaying the main information of the flight from the
1339 PIREP and providing Cancel and Send buttons."""
1340 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1341 xstr("loadPIREP_send_title"),
1342 parent = self._mainWindow,
1343 flags = DIALOG_MODAL)
1344
1345 contentArea = dialog.get_content_area()
1346
1347 label = gtk.Label(xstr("loadPIREP_send_help"))
1348 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1349 xscale = 0.0, yscale = 0.0)
1350 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1351 padding_left = 48, padding_right = 48)
1352 alignment.add(label)
1353 contentArea.pack_start(alignment, False, False, 8)
1354
1355 table = gtk.Table(5, 2)
1356 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1357 xscale = 0.0, yscale = 0.0)
1358 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1359 padding_left = 48, padding_right = 48)
1360 table.set_row_spacings(4)
1361 table.set_col_spacings(16)
1362 tableAlignment.add(table)
1363 contentArea.pack_start(tableAlignment, True, True, 8)
1364
1365 bookedFlight = pirep.bookedFlight
1366
1367 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1368 label.set_use_markup(True)
1369 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1370 xscale = 0.0, yscale = 0.0)
1371 labelAlignment.add(label)
1372 table.attach(labelAlignment, 0, 1, 0, 1)
1373
1374 label = gtk.Label(bookedFlight.callsign)
1375 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1376 xscale = 0.0, yscale = 0.0)
1377 labelAlignment.add(label)
1378 table.attach(labelAlignment, 1, 2, 0, 1)
1379
1380 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1381 label.set_use_markup(True)
1382 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1383 xscale = 0.0, yscale = 0.0)
1384 labelAlignment.add(label)
1385 table.attach(labelAlignment, 0, 1, 1, 2)
1386
1387 label = gtk.Label(str(bookedFlight.departureTime.date()))
1388 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1389 xscale = 0.0, yscale = 0.0)
1390 labelAlignment.add(label)
1391 table.attach(labelAlignment, 1, 2, 1, 2)
1392
1393 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1394 label.set_use_markup(True)
1395 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1396 xscale = 0.0, yscale = 0.0)
1397 labelAlignment.add(label)
1398 table.attach(labelAlignment, 0, 1, 2, 3)
1399
1400 label = gtk.Label(bookedFlight.departureICAO)
1401 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1402 xscale = 0.0, yscale = 0.0)
1403 labelAlignment.add(label)
1404 table.attach(labelAlignment, 1, 2, 2, 3)
1405
1406 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1407 label.set_use_markup(True)
1408 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1409 xscale = 0.0, yscale = 0.0)
1410 labelAlignment.add(label)
1411 table.attach(labelAlignment, 0, 1, 3, 4)
1412
1413 label = gtk.Label(bookedFlight.arrivalICAO)
1414 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1415 xscale = 0.0, yscale = 0.0)
1416 labelAlignment.add(label)
1417 table.attach(labelAlignment, 1, 2, 3, 4)
1418
1419 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1420 label.set_use_markup(True)
1421 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1422 xscale = 0.0, yscale = 0.0)
1423 labelAlignment.add(label)
1424 table.attach(labelAlignment, 0, 1, 4, 5)
1425
1426 rating = pirep.rating
1427 label = gtk.Label()
1428 if rating<0:
1429 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1430 else:
1431 label.set_text("%.1f %%" % (rating,))
1432
1433 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1434 xscale = 0.0, yscale = 0.0)
1435 labelAlignment.add(label)
1436 table.attach(labelAlignment, 1, 2, 4, 5)
1437
1438 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1439 dialog.add_button(xstr("viewPIREP"), 1)
1440 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1441
1442 return dialog
1443
1444 def sendPIREP(self, pirep, callback = None):
1445 """Send the given PIREP."""
1446 self.beginBusy(xstr("sendPIREP_busy"))
1447 self._sendPIREPCallback = callback
1448 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1449
1450 def _pirepSentCallback(self, returned, result):
1451 """Callback for the PIREP sending result."""
1452 gobject.idle_add(self._handlePIREPSent, returned, result)
1453
1454 def _handlePIREPSent(self, returned, result):
1455 """Callback for the PIREP sending result."""
1456 self.endBusy()
1457 secondaryMarkup = None
1458 type = MESSAGETYPE_ERROR
1459 if returned:
1460 if result.success:
1461 type = MESSAGETYPE_INFO
1462 messageFormat = xstr("sendPIREP_success")
1463 secondaryMarkup = xstr("sendPIREP_success_sec")
1464 elif result.alreadyFlown:
1465 messageFormat = xstr("sendPIREP_already")
1466 secondaryMarkup = xstr("sendPIREP_already_sec")
1467 elif result.notAvailable:
1468 messageFormat = xstr("sendPIREP_notavail")
1469 else:
1470 messageFormat = xstr("sendPIREP_unknown")
1471 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1472 else:
1473 print "PIREP sending failed", result
1474 messageFormat = xstr("sendPIREP_failed")
1475 secondaryMarkup = xstr("sendPIREP_failed_sec")
1476
1477 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1478 type = type, message_format = messageFormat)
1479 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1480 dialog.set_title(WINDOW_TITLE_BASE)
1481 if secondaryMarkup is not None:
1482 dialog.format_secondary_markup(secondaryMarkup)
1483
1484 dialog.run()
1485 dialog.hide()
1486
1487 callback = self._sendPIREPCallback
1488 self._sendPIREPCallback = None
1489 if callback is not None:
1490 callback(returned, result)
1491
1492 def sendBugReport(self, summary, description, email, callback = None):
1493 """Send the bug report with the given data."""
1494 description += "\n\n" + ("=" * 40)
1495 description += "\n\nThe contents of the log:\n\n"
1496
1497 for (timestampString, text) in self._logger.lines:
1498 description += unicode(formatFlightLogLine(timestampString, text))
1499
1500 description += "\n\n" + ("=" * 40)
1501 description += "\n\nThe contents of the debug log:\n\n"
1502
1503 buffer = self._debugLogView.get_buffer()
1504 description += buffer.get_text(buffer.get_start_iter(),
1505 buffer.get_end_iter(), True)
1506
1507 self.beginBusy(xstr("sendBugReport_busy"))
1508 self._sendBugReportCallback = callback
1509 self.webHandler.sendBugReport(self._bugReportSentCallback,
1510 summary, description, email)
1511
1512 def _cefInitialized(self):
1513 """Called when CEF has been initialized."""
1514 self._acars.start()
1515 cef.initializeSimBrief()
1516
1517 def _bugReportSentCallback(self, returned, result):
1518 """Callback function for the bug report sending result."""
1519 gobject.idle_add(self._handleBugReportSent, returned, result)
1520
1521 def _handleBugReportSent(self, returned, result):
1522 """Callback for the bug report sending result."""
1523 self.endBusy()
1524 secondaryMarkup = None
1525 type = MESSAGETYPE_ERROR
1526 if returned:
1527 if result.success:
1528 type = MESSAGETYPE_INFO
1529 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1530 secondaryMarkup = xstr("sendBugReport_success_sec")
1531 else:
1532 messageFormat = xstr("sendBugReport_error")
1533 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1534 else:
1535 messageFormat = xstr("sendBugReport_error")
1536 secondaryMarkup = xstr("sendBugReport_error_sec")
1537
1538 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1539 type = type, message_format = messageFormat)
1540 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1541 dialog.set_title(WINDOW_TITLE_BASE)
1542 if secondaryMarkup is not None:
1543 dialog.format_secondary_markup(secondaryMarkup)
1544
1545 dialog.run()
1546 dialog.hide()
1547
1548 callback = self._sendBugReportCallback
1549 self._sendBugReportCallback = None
1550 if callback is not None:
1551 callback(returned, result)
1552
1553 def _listenHotkeys(self):
1554 """Setup the hotkeys based on the configuration."""
1555 if self._hotkeySetID is None and self._simulator is not None:
1556 self._pilotHotkeyIndex = None
1557 self._checklistHotkeyIndex = None
1558
1559 hotkeys = []
1560
1561 config = self.config
1562 if config.enableSounds and config.pilotControlsSounds:
1563 self._pilotHotkeyIndex = len(hotkeys)
1564 hotkeys.append(config.pilotHotkey)
1565
1566 if config.enableChecklists:
1567 self._checklistHotkeyIndex = len(hotkeys)
1568 hotkeys.append(config.checklistHotkey)
1569
1570 if hotkeys:
1571 self._hotkeySetID = \
1572 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1573
1574 def _clearHotkeys(self):
1575 """Clear the hotkeys."""
1576 if self._hotkeySetID is not None:
1577 self._hotkeySetID=None
1578 self._simulator.clearHotkeys()
1579
1580 def _handleHotkeys(self, id, hotkeys):
1581 """Handle the hotkeys."""
1582 if id==self._hotkeySetID:
1583 for index in hotkeys:
1584 if index==self._pilotHotkeyIndex:
1585 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1586 self._flight.pilotHotkeyPressed()
1587 elif index==self._checklistHotkeyIndex:
1588 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1589 self._flight.checklistHotkeyPressed()
1590 else:
1591 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1592
1593 def _showManual(self, menuitem):
1594 """Show the user's manual."""
1595 webbrowser.open(url ="file://" +
1596 os.path.join(self._programDirectory, "doc", "manual",
1597 getLanguage(), "index.html"),
1598 new = 1)
1599
1600 def _showAbout(self, menuitem):
1601 """Show the about dialog."""
1602 dialog = self._getAboutDialog()
1603 dialog.show_all()
1604 dialog.run()
1605 dialog.hide()
1606
1607 def _getAboutDialog(self):
1608 """Get the about dialog.
1609
1610 If it does not exist yet, it will be created."""
1611 if self._aboutDialog is None:
1612 dialog = gtk.AboutDialog()
1613 dialog.set_transient_for(self._mainWindow)
1614 dialog.set_modal(True)
1615
1616 logoPath = os.path.join(self._programDirectory, "logo.png")
1617 logo = pixbuf_new_from_file(logoPath)
1618 dialog.set_logo(logo)
1619
1620 dialog.set_program_name(PROGRAM_NAME)
1621 dialog.set_version(const.VERSION)
1622 dialog.set_copyright("(c) 2012 by István Váradi")
1623 dialog.set_website("http://mlx.varadiistvan.hu")
1624 dialog.set_website_label(xstr("about_website"))
1625
1626 isHungarian = getLanguage()=="hu"
1627 authors = []
1628 for (familyName, firstName, role) in GUI._authors:
1629 author = "%s %s" % \
1630 (familyName if isHungarian else firstName,
1631 firstName if isHungarian else familyName)
1632 role = xstr("about_role_" + role)
1633 authors.append(author + " (" + role + ")")
1634 dialog.set_authors(authors)
1635
1636 dialog.set_license(xstr("about_license"))
1637
1638 if not pygobject:
1639 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1640
1641 self._aboutDialog = dialog
1642
1643 return self._aboutDialog
1644
1645 def _showAboutURL(self, dialog, link, user_data):
1646 """Show the about URL."""
1647 webbrowser.open(url = link, new = 1)
1648
1649 def _setTakeoffAntiIceOn(self, value):
1650 """Set the anti-ice on indicator."""
1651 self._wizard.takeoffAntiIceOn = value
1652
1653 def _setLandingAntiIceOn(self, value):
1654 """Set the anti-ice on indicator."""
1655 self._wizard.landingAntiIceOn = value
1656
1657 def _getCredentialsCallback(self):
1658 """Called when the web handler asks for the credentials."""
1659 # FIXME: this is almost the same as
1660 # SimBriefSetupPage._getCredentialsCallback
1661 with self._credentialsCondition:
1662 self._credentialsAvailable = False
1663
1664 gobject.idle_add(self._getCredentials)
1665
1666 while not self._credentialsAvailable:
1667 self._credentialsCondition.wait()
1668
1669 return (self._credentialsUserName, self._credentialsPassword)
1670
1671 def _getCredentials(self):
1672 """Get the credentials."""
1673 # FIXME: this is almost the same as
1674 # SimBriefSetupPage._getCredentials
1675 with self._credentialsCondition:
1676 config = self.config
1677
1678 dialog = CredentialsDialog(self, config.pilotID, config.password,
1679 xstr("login_title"),
1680 xstr("button_cancel"),
1681 xstr("button_ok"),
1682 xstr("label_pilotID"),
1683 xstr("login_pilotID_tooltip"),
1684 xstr("label_password"),
1685 xstr("login_password_tooltip"),
1686 xstr("login_info"),
1687 config.rememberPassword,
1688 xstr("remember_password"),
1689 xstr("login_remember_tooltip"))
1690 response = dialog.run()
1691
1692 if response==RESPONSETYPE_OK:
1693 self._credentialsUserName = dialog.userName
1694 self._credentialsPassword = dialog.password
1695 rememberPassword = dialog.rememberPassword
1696
1697 config.pilotID = self._credentialsUserName
1698
1699 config.password = \
1700 self._credentialsPassword if rememberPassword else ""
1701 config.rememberPassword = rememberPassword
1702
1703 config.save()
1704 else:
1705 self._credentialsUserName = None
1706 self._credentialsPassword = None
1707
1708 self._credentialsAvailable = True
1709 self._credentialsCondition.notify()
1710
1711 def _updateDone(self):
1712 """Called when the update is done.
1713
1714 It checks if we already know the PID, and if not, asks the user whether
1715 to register."""
1716 if not self.config.pilotID and not self.config.password:
1717 dialog = gtk.MessageDialog(parent = self._mainWindow,
1718 type = MESSAGETYPE_QUESTION,
1719 message_format = xstr("register_ask"))
1720
1721 dialog.set_title(WINDOW_TITLE_BASE)
1722 dialog.format_secondary_markup(xstr("register_ask_sec"))
1723
1724 dialog.add_button(xstr("button_cancel"), 0)
1725 dialog.add_button(xstr("button_register"), 1)
1726 dialog.set_default_response(1)
1727
1728 result = dialog.run()
1729 dialog.hide()
1730 if result == 1:
1731 self._wizard.jumpPage("register")
Note: See TracBrowser for help on using the repository browser.