source: src/mlx/gui/gui.py@ 805:41c3bffe8a25

Last change on this file since 805:41c3bffe8a25 was 805:41c3bffe8a25, checked in by István Váradi <ivaradi@…>, 8 years ago

Simplified SimBrief handling by using the CEF API directly (re #310)

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