source: src/mlx/gui/gui.py@ 746:277ecb58fe9b

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

When so configured, RPC calls are used to communicate with the MAVA website (re #283)

File size: 61.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
450 cef.initialize(self._programDirectory, self._cefInitialized)
451
452 singleton.raiseCallback = self.raiseCallback
453 gtk.main()
454 singleton.raiseCallback = None
455
456 cef.finalize()
457
458 self._disconnect()
459
460 def connected(self, fsType, descriptor):
461 """Called when we have connected to the simulator."""
462 self._connected = True
463 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
464 (const.VERSION, descriptor))
465 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
466 "Welcome to MAVA Logger X " + const.VERSION)
467 gobject.idle_add(self._handleConnected, fsType, descriptor)
468
469 def _handleConnected(self, fsType, descriptor):
470 """Called when the connection to the simulator has succeeded."""
471 self._statusbar.updateConnection(self._connecting, self._connected)
472 self.endBusy()
473 if not self._reconnecting:
474 self._wizard.connected(fsType, descriptor)
475 self._reconnecting = False
476 self._fsType = fsType
477 self._listenHotkeys()
478
479 def connectionFailed(self):
480 """Called when the connection failed."""
481 self._logger.untimedMessage("Connection to the simulator failed")
482 gobject.idle_add(self._connectionFailed)
483
484 def _connectionFailed(self):
485 """Called when the connection failed."""
486 self.endBusy()
487 self._statusbar.updateConnection(self._connecting, self._connected)
488
489 dialog = gtk.MessageDialog(parent = self._mainWindow,
490 type = MESSAGETYPE_ERROR,
491 message_format = xstr("conn_failed"))
492
493 dialog.set_title(WINDOW_TITLE_BASE)
494 dialog.format_secondary_markup(xstr("conn_failed_sec"))
495
496 dialog.add_button(xstr("button_cancel"), 0)
497 dialog.add_button(xstr("button_tryagain"), 1)
498 dialog.set_default_response(1)
499
500 result = dialog.run()
501 dialog.hide()
502 if result == 1:
503 self.beginBusy(xstr("connect_busy"))
504 self._simulator.reconnect()
505 else:
506 self.reset()
507
508 def disconnected(self):
509 """Called when we have disconnected from the simulator."""
510 self._connected = False
511 self._logger.untimedMessage("Disconnected from the simulator")
512 if self._flight is not None:
513 self._flight.disconnected()
514
515 gobject.idle_add(self._disconnected)
516
517 def _disconnected(self):
518 """Called when we have disconnected from the simulator unexpectedly."""
519 self._statusbar.updateConnection(self._connecting, self._connected)
520
521 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
522 message_format = xstr("conn_broken"),
523 parent = self._mainWindow)
524 dialog.set_title(WINDOW_TITLE_BASE)
525 dialog.format_secondary_markup(xstr("conn_broken_sec"))
526
527 dialog.add_button(xstr("button_cancel"), 0)
528 dialog.add_button(xstr("button_reconnect"), 1)
529 dialog.set_default_response(1)
530
531 result = dialog.run()
532 dialog.hide()
533 if result == 1:
534 self.beginBusy(xstr("connect_busy"))
535 self._reconnecting = True
536 self._simulator.reconnect()
537 else:
538 self.reset()
539
540 def enableFlightInfo(self, aircraftType):
541 """Enable the flight info tab."""
542 self._flightInfo.enable(aircraftType)
543
544 def cancelFlight(self):
545 """Cancel the current file, if the user confirms it."""
546 dialog = gtk.MessageDialog(parent = self._mainWindow,
547 type = MESSAGETYPE_QUESTION,
548 message_format = xstr("cancelFlight_question"))
549
550 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
551 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
552
553 dialog.set_title(WINDOW_TITLE_BASE)
554 result = dialog.run()
555 dialog.hide()
556
557 if result==RESPONSETYPE_YES:
558 self.reset()
559
560 def reset(self):
561 """Reset the GUI."""
562 self._disconnect()
563
564 self._simulator = None
565
566 self._flightInfo.reset()
567 self._flightInfo.disable()
568 self.resetFlightStatus()
569
570 self._weightHelp.reset()
571 self._weightHelp.disable()
572 self._notebook.set_current_page(0)
573
574 self._logView.get_buffer().set_text("")
575
576 if self.loggedIn:
577 self._wizard.reloadFlights(self._handleReloadResult)
578 else:
579 self._wizard.reset(None)
580
581 def _handleReloadResult(self, returned, result):
582 """Handle the result of the reloading of the flights."""
583 self._wizard.reset(result if returned and result.loggedIn else None)
584
585 def _disconnect(self, closingMessage = None, duration = 3):
586 """Disconnect from the simulator if connected."""
587 self.stopMonitoring()
588 self._clearHotkeys()
589
590 if self._connected:
591 if closingMessage is None:
592 self._flight.simulator.disconnect()
593 else:
594 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
595 closingMessage, duration,
596 disconnect = True)
597 self._connected = False
598
599 self._connecting = False
600 self._reconnecting = False
601 self._statusbar.updateConnection(False, False)
602 self._weightHelp.disable()
603
604 return True
605
606 def insertFlightLogLine(self, index, timestampString, text, isFault):
607 """Insert the flight log line with the given data."""
608 gobject.idle_add(self._insertFlightLogLine, index,
609 formatFlightLogLine(timestampString, text),
610 isFault)
611
612 def _insertFlightLogLine(self, index, line, isFault):
613 """Perform the real insertion.
614
615 To be called from the event loop."""
616 buffer = self._logView.get_buffer()
617 lineIter = buffer.get_iter_at_line(index)
618 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
619 self._logView.scroll_mark_onscreen(buffer.get_insert())
620
621 def removeFlightLogLine(self, index):
622 """Remove the flight log line with the given index."""
623 gobject.idle_add(self._removeFlightLogLine, index)
624
625 def addFault(self, id, timestampString, text):
626 """Add a fault to the list of faults."""
627 faultText = formatFlightLogLine(timestampString, text).strip()
628 gobject.idle_add(self._flightInfo.addFault, id, faultText)
629
630 def updateFault(self, id, timestampString, text):
631 """Update a fault in the list of faults."""
632 faultText = formatFlightLogLine(timestampString, text).strip()
633 gobject.idle_add(self._flightInfo.updateFault, id, faultText)
634
635 def clearFault(self, id):
636 """Clear a fault in the list of faults."""
637 gobject.idle_add(self._flightInfo.clearFault, id)
638
639 def _removeFlightLogLine(self, index):
640 """Perform the real removal."""
641 buffer = self._logView.get_buffer()
642 startIter = buffer.get_iter_at_line(index)
643 endIter = buffer.get_iter_at_line(index+1)
644 buffer.delete(startIter, endIter)
645 self._logView.scroll_mark_onscreen(buffer.get_insert())
646
647 def check(self, flight, aircraft, logger, oldState, state):
648 """Update the data."""
649 gobject.idle_add(self._monitorWindow.setData, state)
650 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
651
652 def resetFlightStatus(self):
653 """Reset the status of the flight."""
654 self._statusbar.resetFlightStatus()
655 self._statusbar.updateTime()
656 self._statusIcon.resetFlightStatus()
657
658 def setStage(self, stage):
659 """Set the stage of the flight."""
660 gobject.idle_add(self._setStage, stage)
661
662 def _setStage(self, stage):
663 """Set the stage of the flight."""
664 self._statusbar.setStage(stage)
665 self._statusIcon.setStage(stage)
666 self._wizard.setStage(stage)
667 if stage==const.STAGE_END:
668 welcomeMessage = \
669 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
670 self._disconnect(closingMessage =
671 "Flight plan closed. " + welcomeMessage,
672 duration = 5)
673
674 def setRating(self, rating):
675 """Set the rating of the flight."""
676 gobject.idle_add(self._setRating, rating)
677
678 def _setRating(self, rating):
679 """Set the rating of the flight."""
680 self._statusbar.setRating(rating)
681 self._statusIcon.setRating(rating)
682
683 def setNoGo(self, reason):
684 """Set the rating of the flight to No-Go with the given reason."""
685 gobject.idle_add(self._setNoGo, reason)
686
687 def _setNoGo(self, reason):
688 """Set the rating of the flight."""
689 self._statusbar.setNoGo(reason)
690 self._statusIcon.setNoGo(reason)
691
692 def _handleMainWindowState(self, window, event):
693 """Hande a change in the state of the window"""
694 iconified = gdk.WindowState.ICONIFIED if pygobject \
695 else gdk.WINDOW_STATE_ICONIFIED
696
697 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
698 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
699 self._statusIcon.mainWindowHidden()
700 else:
701 self._statusIcon.mainWindowShown()
702
703 if self.config.hideMinimizedWindow and not pygobject and \
704 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
705 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
706 self.hideMainWindow(savePosition = False)
707 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
708 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
709 self._mainWindow.present()
710
711 def _handleLeaveNotify(self, widget, event):
712 """Handle the leave-notify event.
713
714 Here we reset the focus to the main window as CEF might have acquired
715 it earlier."""
716 self._mainWindow.get_window().focus()
717
718 def raiseCallback(self):
719 """Callback for the singleton handling code."""
720 gobject.idle_add(self.raiseMainWindow)
721
722 def raiseMainWindow(self):
723 """Show the main window if invisible, and raise it."""
724 if not self._mainWindow.get_visible():
725 self.showMainWindow()
726 self._mainWindow.present()
727
728 def deleteMainWindow(self, window, event):
729 """Handle the delete event for the main window."""
730 if self.config.quitOnClose:
731 self._quit()
732 else:
733 self.hideMainWindow()
734 return True
735
736 def hideMainWindow(self, savePosition = True):
737 """Hide the main window and save its position."""
738 if savePosition:
739 (self._mainWindowX, self._mainWindowY) = \
740 self._mainWindow.get_window().get_root_origin()
741 else:
742 self._mainWindowX = self._mainWindowY = None
743 self._mainWindow.hide()
744 return True
745
746 def showMainWindow(self):
747 """Show the main window at its former position."""
748 if self._mainWindowX is not None and self._mainWindowY is not None:
749 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
750
751 if pygobject:
752 self._mainWindow.show()
753 else:
754 self._mainWindow.present()
755 self._mainWindow.deiconify()
756
757 def toggleMainWindow(self):
758 """Toggle the main window."""
759 if self._mainWindow.get_visible():
760 self.hideMainWindow()
761 else:
762 self.showMainWindow()
763
764 def hideMonitorWindow(self, savePosition = True):
765 """Hide the monitor window."""
766 if savePosition:
767 (self._monitorWindowX, self._monitorWindowY) = \
768 self._monitorWindow.get_window().get_root_origin()
769 else:
770 self._monitorWindowX = self._monitorWindowY = None
771 self._monitorWindow.hide()
772 self._statusIcon.monitorWindowHidden()
773 if self._showMonitorMenuItem.get_active():
774 self._selfToggling = True
775 self._showMonitorMenuItem.set_active(False)
776 return True
777
778 def showMonitorWindow(self):
779 """Show the monitor window."""
780 if self._monitorWindowX is not None and self._monitorWindowY is not None:
781 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
782 self._monitorWindow.show_all()
783 self._statusIcon.monitorWindowShown()
784 if not self._showMonitorMenuItem.get_active():
785 self._selfToggling = True
786 self._showMonitorMenuItem.set_active(True)
787
788 def _toggleMonitorWindow(self, menuItem):
789 if self._selfToggling:
790 self._selfToggling = False
791 elif self._monitorWindow.get_visible():
792 self.hideMonitorWindow()
793 else:
794 self.showMonitorWindow()
795
796 def restart(self):
797 """Quit and restart the application."""
798 self.toRestart = True
799 self._quit(force = True)
800
801 def flushStdIO(self):
802 """Flush any text to the standard error that could not be logged."""
803 if self._stdioText:
804 sys.__stderr__.write(self._stdioText)
805
806 def writeStdIO(self, text):
807 """Write the given text into standard I/O log."""
808 with self._stdioLock:
809 self._stdioText += text
810
811 gobject.idle_add(self._writeStdIO)
812
813 def beginBusy(self, message):
814 """Begin a period of background processing."""
815 self._wizard.set_sensitive(False)
816 self._weightHelp.set_sensitive(False)
817 self._mainWindow.get_window().set_cursor(self._busyCursor)
818 self._statusbar.updateBusyState(message)
819
820 def updateBusyState(self, message):
821 """Update the busy state."""
822 self._statusbar.updateBusyState(message)
823
824 def endBusy(self):
825 """End a period of background processing."""
826 self._mainWindow.get_window().set_cursor(None)
827 self._weightHelp.set_sensitive(True)
828 self._wizard.set_sensitive(True)
829 self._statusbar.updateBusyState(None)
830
831 def initializeWeightHelp(self):
832 """Initialize the weight help tab."""
833 self._weightHelp.reset()
834 self._weightHelp.enable()
835
836 def getFleetAsync(self, callback = None, force = None):
837 """Get the fleet asynchronously."""
838 gobject.idle_add(self.getFleet, callback, force)
839
840 def getFleet(self, callback = None, force = False):
841 """Get the fleet.
842
843 If force is False, and we already have a fleet retrieved,
844 that one will be used."""
845 if self._fleet is None or force:
846 self._fleetCallback = callback
847 self.beginBusy(xstr("fleet_busy"))
848 self.webHandler.getFleet(self._fleetResultCallback)
849 else:
850 callback(self._fleet)
851
852 def commentsChanged(self):
853 """Indicate that the comments have changed."""
854 self._wizard.commentsChanged()
855
856 def delayCodesChanged(self):
857 """Called when the delay codes have changed."""
858 self._wizard.delayCodesChanged()
859
860 def faultExplanationsChanged(self):
861 """Called when the status of the explanations of the faults have
862 changed."""
863 self._wizard.faultExplanationsChanged()
864
865 def updateRTO(self, inLoop = False):
866 """Indicate that the RTO state should be updated."""
867 if inLoop:
868 self._wizard.updateRTO()
869 else:
870 gobject.idle_add(self.updateRTO, True)
871
872 def rtoToggled(self, indicated):
873 """Called when the user has toggled the RTO checkbox."""
874 self._flight.rtoToggled(indicated)
875
876 def _fleetResultCallback(self, returned, result):
877 """Called when the fleet has been queried."""
878 gobject.idle_add(self._handleFleetResult, returned, result)
879
880 def _handleFleetResult(self, returned, result):
881 """Handle the fleet result."""
882 self.endBusy()
883 if returned:
884 self._fleet = result.fleet
885 else:
886 self._fleet = None
887
888 dialog = gtk.MessageDialog(parent = self.mainWindow,
889 type = MESSAGETYPE_ERROR,
890 message_format = xstr("fleet_failed"))
891 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
892 dialog.set_title(WINDOW_TITLE_BASE)
893 dialog.run()
894 dialog.hide()
895
896 callback = self._fleetCallback
897 self._fleetCallback = None
898 if callback is not None:
899 callback(self._fleet)
900 self._fleetGateStatus.handleFleet(self._fleet)
901
902 def updatePlane(self, tailNumber, status,
903 gateNumber = None, callback = None):
904 """Update the status of the given plane."""
905 self.beginBusy(xstr("fleet_update_busy"))
906
907 self._updatePlaneCallback = callback
908
909 self._updatePlaneTailNumber = tailNumber
910 self._updatePlaneStatus = status
911 self._updatePlaneGateNumber = gateNumber
912
913 self.webHandler.updatePlane(self._updatePlaneResultCallback,
914 tailNumber, status, gateNumber)
915
916 def _updatePlaneResultCallback(self, returned, result):
917 """Called when the status of a plane has been updated."""
918 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
919
920 def _handleUpdatePlaneResult(self, returned, result):
921 """Handle the plane update result."""
922 self.endBusy()
923 if returned:
924 success = result.success
925 if success:
926 if self._fleet is not None:
927 self._fleet.updatePlane(self._updatePlaneTailNumber,
928 self._updatePlaneStatus,
929 self._updatePlaneGateNumber)
930 self._fleetGateStatus.handleFleet(self._fleet)
931 else:
932 dialog = gtk.MessageDialog(parent = self.mainWindow,
933 type = MESSAGETYPE_ERROR,
934 message_format = xstr("fleet_update_failed"))
935 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
936 dialog.set_title(WINDOW_TITLE_BASE)
937 dialog.run()
938 dialog.hide()
939
940 success = None
941
942 callback = self._updatePlaneCallback
943 self._updatePlaneCallback = None
944 if callback is not None:
945 callback(success)
946
947 def _writeStdIO(self):
948 """Perform the real writing."""
949 with self._stdioLock:
950 text = self._stdioText
951 self._stdioText = ""
952 if not text: return
953
954 lines = text.splitlines()
955 if text[-1]=="\n":
956 text = ""
957 else:
958 text = lines[-1]
959 lines = lines[:-1]
960
961 now = datetime.datetime.now()
962 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
963
964 for line in lines:
965 #print >> sys.__stdout__, line
966 if self._stdioStartingLine:
967 self._writeLog(timeStr, self._debugLogView)
968 self._writeLog(line + "\n", self._debugLogView)
969 self._stdioStartingLine = True
970
971 if text:
972 #print >> sys.__stdout__, text,
973 if self._stdioStartingLine:
974 self._writeLog(timeStr, self._debugLogView)
975 self._writeLog(text, self._debugLogView)
976 self._stdioStartingLine = False
977
978 def connectSimulator(self, aircraftType, simulatorType):
979 """Connect to the simulator for the first time."""
980 self._logger.reset()
981
982 self._flight = flight.Flight(self._logger, self)
983 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
984 self._flight.aircraftType = aircraftType
985 self._flight.aircraft = acft.Aircraft.create(self._flight)
986 self._flight.aircraft._checkers.append(self)
987
988 if self._simulator is None:
989 self._simulator = fs.createSimulator(simulatorType, self)
990 fs.setupMessageSending(self.config, self._simulator)
991 self._setupTimeSync()
992
993 self._flight.simulator = self._simulator
994
995 self.beginBusy(xstr("connect_busy"))
996 self._statusbar.updateConnection(self._connecting, self._connected)
997
998 self._connecting = True
999 self._simulator.connect(self._flight.aircraft)
1000
1001 def startMonitoring(self):
1002 """Start monitoring."""
1003 if not self._monitoring:
1004 self.simulator.startMonitoring()
1005 self._monitoring = True
1006
1007 def stopMonitoring(self):
1008 """Stop monitoring."""
1009 if self._monitoring:
1010 self.simulator.stopMonitoring()
1011 self._monitoring = False
1012
1013 def cruiseLevelChanged(self):
1014 """Called when the cruise level is changed in the flight wizard."""
1015 if self._flight is not None:
1016 return self._flight.cruiseLevelChanged()
1017 else:
1018 return False
1019
1020 def _buildMenuBar(self, accelGroup):
1021 """Build the main menu bar."""
1022 menuBar = gtk.MenuBar()
1023
1024 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
1025 fileMenu = gtk.Menu()
1026 fileMenuItem.set_submenu(fileMenu)
1027 menuBar.append(fileMenuItem)
1028
1029 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
1030 loadPIREPMenuItem.set_use_stock(True)
1031 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
1032 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
1033 ord(xstr("menu_file_loadPIREP_key")),
1034 CONTROL_MASK, ACCEL_VISIBLE)
1035 loadPIREPMenuItem.connect("activate", self._loadPIREP)
1036 fileMenu.append(loadPIREPMenuItem)
1037
1038 fileMenu.append(gtk.SeparatorMenuItem())
1039
1040 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
1041 quitMenuItem.set_use_stock(True)
1042 quitMenuItem.set_label(xstr("menu_file_quit"))
1043 quitMenuItem.add_accelerator("activate", accelGroup,
1044 ord(xstr("menu_file_quit_key")),
1045 CONTROL_MASK, ACCEL_VISIBLE)
1046 quitMenuItem.connect("activate", self._quit)
1047 fileMenu.append(quitMenuItem)
1048
1049 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
1050 toolsMenu = gtk.Menu()
1051 toolsMenuItem.set_submenu(toolsMenu)
1052 menuBar.append(toolsMenuItem)
1053
1054 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
1055 checklistMenuItem.set_use_stock(True)
1056 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
1057 checklistMenuItem.add_accelerator("activate", accelGroup,
1058 ord(xstr("menu_tools_chklst_key")),
1059 CONTROL_MASK, ACCEL_VISIBLE)
1060 checklistMenuItem.connect("activate", self._editChecklist)
1061 toolsMenu.append(checklistMenuItem)
1062
1063 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
1064 approachCalloutsMenuItem.set_use_stock(True)
1065 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
1066 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
1067 ord(xstr("menu_tools_callouts_key")),
1068 CONTROL_MASK, ACCEL_VISIBLE)
1069 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
1070 toolsMenu.append(approachCalloutsMenuItem)
1071
1072 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
1073 prefsMenuItem.set_use_stock(True)
1074 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
1075 prefsMenuItem.add_accelerator("activate", accelGroup,
1076 ord(xstr("menu_tools_prefs_key")),
1077 CONTROL_MASK, ACCEL_VISIBLE)
1078 prefsMenuItem.connect("activate", self._editPreferences)
1079 toolsMenu.append(prefsMenuItem)
1080
1081 toolsMenu.append(gtk.SeparatorMenuItem())
1082
1083 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
1084 bugReportMenuItem.set_use_stock(True)
1085 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
1086 bugReportMenuItem.add_accelerator("activate", accelGroup,
1087 ord(xstr("menu_tools_bugreport_key")),
1088 CONTROL_MASK, ACCEL_VISIBLE)
1089 bugReportMenuItem.connect("activate", self._reportBug)
1090 toolsMenu.append(bugReportMenuItem)
1091
1092 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
1093 viewMenu = gtk.Menu()
1094 viewMenuItem.set_submenu(viewMenu)
1095 menuBar.append(viewMenuItem)
1096
1097 self._showMonitorMenuItem = gtk.CheckMenuItem()
1098 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
1099 self._showMonitorMenuItem.set_use_underline(True)
1100 self._showMonitorMenuItem.set_active(False)
1101 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
1102 ord(xstr("menu_view_monitor_key")),
1103 CONTROL_MASK, ACCEL_VISIBLE)
1104 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1105 viewMenu.append(self._showMonitorMenuItem)
1106
1107 showDebugMenuItem = gtk.CheckMenuItem()
1108 showDebugMenuItem.set_label(xstr("menu_view_debug"))
1109 showDebugMenuItem.set_use_underline(True)
1110 showDebugMenuItem.set_active(False)
1111 showDebugMenuItem.add_accelerator("activate", accelGroup,
1112 ord(xstr("menu_view_debug_key")),
1113 CONTROL_MASK, ACCEL_VISIBLE)
1114 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1115 viewMenu.append(showDebugMenuItem)
1116
1117 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1118 helpMenu = gtk.Menu()
1119 helpMenuItem.set_submenu(helpMenu)
1120 menuBar.append(helpMenuItem)
1121
1122 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1123 manualMenuItem.set_use_stock(True)
1124 manualMenuItem.set_label(xstr("menu_help_manual"))
1125 manualMenuItem.add_accelerator("activate", accelGroup,
1126 ord(xstr("menu_help_manual_key")),
1127 CONTROL_MASK, ACCEL_VISIBLE)
1128 manualMenuItem.connect("activate", self._showManual)
1129 helpMenu.append(manualMenuItem)
1130
1131 helpMenu.append(gtk.SeparatorMenuItem())
1132
1133 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1134 aboutMenuItem.set_use_stock(True)
1135 aboutMenuItem.set_label(xstr("menu_help_about"))
1136 aboutMenuItem.add_accelerator("activate", accelGroup,
1137 ord(xstr("menu_help_about_key")),
1138 CONTROL_MASK, ACCEL_VISIBLE)
1139 aboutMenuItem.connect("activate", self._showAbout)
1140 helpMenu.append(aboutMenuItem)
1141
1142 return menuBar
1143
1144 def _toggleDebugLog(self, menuItem):
1145 """Toggle the debug log."""
1146 if menuItem.get_active():
1147 label = gtk.Label(xstr("tab_debug_log"))
1148 label.set_use_underline(True)
1149 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
1150 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1151 self._notebook.set_current_page(self._debugLogPage)
1152 else:
1153 self._notebook.remove_page(self._debugLogPage)
1154
1155 def _buildLogWidget(self):
1156 """Build the widget for the log."""
1157 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1158
1159 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1160 padding_left = 16, padding_right = 16)
1161
1162 logScroller = gtk.ScrolledWindow()
1163 # FIXME: these should be constants in common
1164 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1165 else gtk.POLICY_AUTOMATIC,
1166 gtk.PolicyType.AUTOMATIC if pygobject
1167 else gtk.POLICY_AUTOMATIC)
1168 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1169 else gtk.SHADOW_IN)
1170 logView = gtk.TextView()
1171 logView.set_editable(False)
1172 logView.set_cursor_visible(False)
1173 logScroller.add(logView)
1174
1175 logBox = gtk.VBox()
1176 logBox.pack_start(logScroller, True, True, 0)
1177 logBox.set_size_request(-1, 200)
1178
1179 alignment.add(logBox)
1180
1181 return (alignment, logView)
1182
1183 def _writeLog(self, msg, logView, isFault = False):
1184 """Write the given message to the log."""
1185 buffer = logView.get_buffer()
1186 appendTextBuffer(buffer, msg, isFault = isFault)
1187 logView.scroll_mark_onscreen(buffer.get_insert())
1188
1189 def _quit(self, what = None, force = False):
1190 """Quit from the application."""
1191 if force:
1192 result=RESPONSETYPE_YES
1193 else:
1194 dialog = gtk.MessageDialog(parent = self._mainWindow,
1195 type = MESSAGETYPE_QUESTION,
1196 message_format = xstr("quit_question"))
1197
1198 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1199 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1200
1201 dialog.set_title(WINDOW_TITLE_BASE)
1202 result = dialog.run()
1203 dialog.hide()
1204
1205 if result==RESPONSETYPE_YES:
1206 self._statusIcon.destroy()
1207 return gtk.main_quit()
1208
1209 def _notebookPageSwitch(self, notebook, page, page_num):
1210 """Called when the current page of the notebook has changed."""
1211 if page_num==0:
1212 gobject.idle_add(self._wizard.grabDefault)
1213 else:
1214 self._mainWindow.set_default(None)
1215
1216 def isWizardActive(self):
1217 """Determine if the flight wizard is active."""
1218 return self._notebook.get_current_page()==0
1219
1220 def _editChecklist(self, menuItem):
1221 """Callback for editing the checklists."""
1222 self._checklistEditor.run()
1223
1224 def _editApproachCallouts(self, menuItem):
1225 """Callback for editing the approach callouts."""
1226 self._approachCalloutsEditor.run()
1227
1228 def _editPreferences(self, menuItem):
1229 """Callback for editing the preferences."""
1230 self._clearHotkeys()
1231 self._preferences.run(self.config)
1232 self._setupTimeSync()
1233 self._listenHotkeys()
1234
1235 def _reportBug(self, menuItem):
1236 """Callback for reporting a bug."""
1237 self._bugReportDialog.run()
1238
1239 def _setupTimeSync(self):
1240 """Enable or disable the simulator time synchronization based on the
1241 configuration."""
1242 simulator = self._simulator
1243 if simulator is not None:
1244 if self.config.syncFSTime:
1245 simulator.enableTimeSync()
1246 else:
1247 simulator.disableTimeSync()
1248
1249 def _loadPIREP(self, menuItem):
1250 """Load a PIREP for sending."""
1251 dialog = self._getLoadPirepDialog()
1252
1253 if self._lastLoadedPIREP:
1254 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1255 else:
1256 pirepDirectory = self.config.pirepDirectory
1257 if pirepDirectory is not None:
1258 dialog.set_current_folder(pirepDirectory)
1259
1260 result = dialog.run()
1261 dialog.hide()
1262
1263 if result==RESPONSETYPE_OK:
1264 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1265
1266 pirep = PIREP.load(self._lastLoadedPIREP)
1267 if pirep is None:
1268 dialog = gtk.MessageDialog(parent = self._mainWindow,
1269 type = MESSAGETYPE_ERROR,
1270 message_format = xstr("loadPIREP_failed"))
1271 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1272 dialog.set_title(WINDOW_TITLE_BASE)
1273 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1274 dialog.run()
1275 dialog.hide()
1276 else:
1277 dialog = self._getSendLoadedDialog(pirep)
1278 dialog.show_all()
1279 while True:
1280 result = dialog.run()
1281
1282 if result==RESPONSETYPE_OK:
1283 self.sendPIREP(pirep)
1284 elif result==1:
1285 self._pirepViewer.setPIREP(pirep)
1286 self._pirepViewer.show_all()
1287 self._pirepViewer.run()
1288 self._pirepViewer.hide()
1289 else:
1290 break
1291
1292 dialog.hide()
1293
1294 def _getLoadPirepDialog(self):
1295 """Get the PIREP loading file chooser dialog.
1296
1297 If it is not created yet, it will be created."""
1298 if self._loadPIREPDialog is None:
1299 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1300 xstr("loadPIREP_browser_title"),
1301 action = FILE_CHOOSER_ACTION_OPEN,
1302 buttons = (gtk.STOCK_CANCEL,
1303 RESPONSETYPE_CANCEL,
1304 gtk.STOCK_OK, RESPONSETYPE_OK),
1305 parent = self._mainWindow)
1306 dialog.set_modal(True)
1307
1308
1309 filter = gtk.FileFilter()
1310 filter.set_name(xstr("file_filter_pireps"))
1311 filter.add_pattern("*.pirep")
1312 dialog.add_filter(filter)
1313
1314 filter = gtk.FileFilter()
1315 filter.set_name(xstr("file_filter_all"))
1316 filter.add_pattern("*.*")
1317 dialog.add_filter(filter)
1318
1319 self._loadPIREPDialog = dialog
1320
1321 return self._loadPIREPDialog
1322
1323 def _getSendLoadedDialog(self, pirep):
1324 """Get a dialog displaying the main information of the flight from the
1325 PIREP and providing Cancel and Send buttons."""
1326 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1327 xstr("loadPIREP_send_title"),
1328 parent = self._mainWindow,
1329 flags = DIALOG_MODAL)
1330
1331 contentArea = dialog.get_content_area()
1332
1333 label = gtk.Label(xstr("loadPIREP_send_help"))
1334 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1335 xscale = 0.0, yscale = 0.0)
1336 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1337 padding_left = 48, padding_right = 48)
1338 alignment.add(label)
1339 contentArea.pack_start(alignment, False, False, 8)
1340
1341 table = gtk.Table(5, 2)
1342 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1343 xscale = 0.0, yscale = 0.0)
1344 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1345 padding_left = 48, padding_right = 48)
1346 table.set_row_spacings(4)
1347 table.set_col_spacings(16)
1348 tableAlignment.add(table)
1349 contentArea.pack_start(tableAlignment, True, True, 8)
1350
1351 bookedFlight = pirep.bookedFlight
1352
1353 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1354 label.set_use_markup(True)
1355 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1356 xscale = 0.0, yscale = 0.0)
1357 labelAlignment.add(label)
1358 table.attach(labelAlignment, 0, 1, 0, 1)
1359
1360 label = gtk.Label(bookedFlight.callsign)
1361 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1362 xscale = 0.0, yscale = 0.0)
1363 labelAlignment.add(label)
1364 table.attach(labelAlignment, 1, 2, 0, 1)
1365
1366 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1367 label.set_use_markup(True)
1368 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1369 xscale = 0.0, yscale = 0.0)
1370 labelAlignment.add(label)
1371 table.attach(labelAlignment, 0, 1, 1, 2)
1372
1373 label = gtk.Label(str(bookedFlight.departureTime.date()))
1374 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1375 xscale = 0.0, yscale = 0.0)
1376 labelAlignment.add(label)
1377 table.attach(labelAlignment, 1, 2, 1, 2)
1378
1379 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1380 label.set_use_markup(True)
1381 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1382 xscale = 0.0, yscale = 0.0)
1383 labelAlignment.add(label)
1384 table.attach(labelAlignment, 0, 1, 2, 3)
1385
1386 label = gtk.Label(bookedFlight.departureICAO)
1387 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1388 xscale = 0.0, yscale = 0.0)
1389 labelAlignment.add(label)
1390 table.attach(labelAlignment, 1, 2, 2, 3)
1391
1392 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1393 label.set_use_markup(True)
1394 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1395 xscale = 0.0, yscale = 0.0)
1396 labelAlignment.add(label)
1397 table.attach(labelAlignment, 0, 1, 3, 4)
1398
1399 label = gtk.Label(bookedFlight.arrivalICAO)
1400 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1401 xscale = 0.0, yscale = 0.0)
1402 labelAlignment.add(label)
1403 table.attach(labelAlignment, 1, 2, 3, 4)
1404
1405 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1406 label.set_use_markup(True)
1407 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1408 xscale = 0.0, yscale = 0.0)
1409 labelAlignment.add(label)
1410 table.attach(labelAlignment, 0, 1, 4, 5)
1411
1412 rating = pirep.rating
1413 label = gtk.Label()
1414 if rating<0:
1415 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1416 else:
1417 label.set_text("%.1f %%" % (rating,))
1418
1419 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1420 xscale = 0.0, yscale = 0.0)
1421 labelAlignment.add(label)
1422 table.attach(labelAlignment, 1, 2, 4, 5)
1423
1424 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1425 dialog.add_button(xstr("viewPIREP"), 1)
1426 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1427
1428 return dialog
1429
1430 def sendPIREP(self, pirep, callback = None):
1431 """Send the given PIREP."""
1432 self.beginBusy(xstr("sendPIREP_busy"))
1433 self._sendPIREPCallback = callback
1434 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1435
1436 def _pirepSentCallback(self, returned, result):
1437 """Callback for the PIREP sending result."""
1438 gobject.idle_add(self._handlePIREPSent, returned, result)
1439
1440 def _handlePIREPSent(self, returned, result):
1441 """Callback for the PIREP sending result."""
1442 self.endBusy()
1443 secondaryMarkup = None
1444 type = MESSAGETYPE_ERROR
1445 if returned:
1446 if result.success:
1447 type = MESSAGETYPE_INFO
1448 messageFormat = xstr("sendPIREP_success")
1449 secondaryMarkup = xstr("sendPIREP_success_sec")
1450 elif result.alreadyFlown:
1451 messageFormat = xstr("sendPIREP_already")
1452 secondaryMarkup = xstr("sendPIREP_already_sec")
1453 elif result.notAvailable:
1454 messageFormat = xstr("sendPIREP_notavail")
1455 else:
1456 messageFormat = xstr("sendPIREP_unknown")
1457 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1458 else:
1459 print "PIREP sending failed", result
1460 messageFormat = xstr("sendPIREP_failed")
1461 secondaryMarkup = xstr("sendPIREP_failed_sec")
1462
1463 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1464 type = type, message_format = messageFormat)
1465 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1466 dialog.set_title(WINDOW_TITLE_BASE)
1467 if secondaryMarkup is not None:
1468 dialog.format_secondary_markup(secondaryMarkup)
1469
1470 dialog.run()
1471 dialog.hide()
1472
1473 callback = self._sendPIREPCallback
1474 self._sendPIREPCallback = None
1475 if callback is not None:
1476 callback(returned, result)
1477
1478 def sendBugReport(self, summary, description, email, callback = None):
1479 """Send the bug report with the given data."""
1480 description += "\n\n" + ("=" * 40)
1481 description += "\n\nThe contents of the log:\n\n"
1482
1483 for (timestampString, text) in self._logger.lines:
1484 description += unicode(formatFlightLogLine(timestampString, text))
1485
1486 description += "\n\n" + ("=" * 40)
1487 description += "\n\nThe contents of the debug log:\n\n"
1488
1489 buffer = self._debugLogView.get_buffer()
1490 description += buffer.get_text(buffer.get_start_iter(),
1491 buffer.get_end_iter(), True)
1492
1493 self.beginBusy(xstr("sendBugReport_busy"))
1494 self._sendBugReportCallback = callback
1495 self.webHandler.sendBugReport(self._bugReportSentCallback,
1496 summary, description, email)
1497
1498 def _cefInitialized(self):
1499 """Called when CEF has been initialized."""
1500 self._acars.start()
1501 cef.initializeSimBrief()
1502
1503 def _bugReportSentCallback(self, returned, result):
1504 """Callback function for the bug report sending result."""
1505 gobject.idle_add(self._handleBugReportSent, returned, result)
1506
1507 def _handleBugReportSent(self, returned, result):
1508 """Callback for the bug report sending result."""
1509 self.endBusy()
1510 secondaryMarkup = None
1511 type = MESSAGETYPE_ERROR
1512 if returned:
1513 if result.success:
1514 type = MESSAGETYPE_INFO
1515 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1516 secondaryMarkup = xstr("sendBugReport_success_sec")
1517 else:
1518 messageFormat = xstr("sendBugReport_error")
1519 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1520 else:
1521 messageFormat = xstr("sendBugReport_error")
1522 secondaryMarkup = xstr("sendBugReport_error_sec")
1523
1524 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1525 type = type, message_format = messageFormat)
1526 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1527 dialog.set_title(WINDOW_TITLE_BASE)
1528 if secondaryMarkup is not None:
1529 dialog.format_secondary_markup(secondaryMarkup)
1530
1531 dialog.run()
1532 dialog.hide()
1533
1534 callback = self._sendBugReportCallback
1535 self._sendBugReportCallback = None
1536 if callback is not None:
1537 callback(returned, result)
1538
1539 def _listenHotkeys(self):
1540 """Setup the hotkeys based on the configuration."""
1541 if self._hotkeySetID is None and self._simulator is not None:
1542 self._pilotHotkeyIndex = None
1543 self._checklistHotkeyIndex = None
1544
1545 hotkeys = []
1546
1547 config = self.config
1548 if config.enableSounds and config.pilotControlsSounds:
1549 self._pilotHotkeyIndex = len(hotkeys)
1550 hotkeys.append(config.pilotHotkey)
1551
1552 if config.enableChecklists:
1553 self._checklistHotkeyIndex = len(hotkeys)
1554 hotkeys.append(config.checklistHotkey)
1555
1556 if hotkeys:
1557 self._hotkeySetID = \
1558 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1559
1560 def _clearHotkeys(self):
1561 """Clear the hotkeys."""
1562 if self._hotkeySetID is not None:
1563 self._hotkeySetID=None
1564 self._simulator.clearHotkeys()
1565
1566 def _handleHotkeys(self, id, hotkeys):
1567 """Handle the hotkeys."""
1568 if id==self._hotkeySetID:
1569 for index in hotkeys:
1570 if index==self._pilotHotkeyIndex:
1571 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1572 self._flight.pilotHotkeyPressed()
1573 elif index==self._checklistHotkeyIndex:
1574 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1575 self._flight.checklistHotkeyPressed()
1576 else:
1577 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1578
1579 def _showManual(self, menuitem):
1580 """Show the user's manual."""
1581 webbrowser.open(url ="file://" +
1582 os.path.join(self._programDirectory, "doc", "manual",
1583 getLanguage(), "index.html"),
1584 new = 1)
1585
1586 def _showAbout(self, menuitem):
1587 """Show the about dialog."""
1588 dialog = self._getAboutDialog()
1589 dialog.show_all()
1590 dialog.run()
1591 dialog.hide()
1592
1593 def _getAboutDialog(self):
1594 """Get the about dialog.
1595
1596 If it does not exist yet, it will be created."""
1597 if self._aboutDialog is None:
1598 dialog = gtk.AboutDialog()
1599 dialog.set_transient_for(self._mainWindow)
1600 dialog.set_modal(True)
1601
1602 logoPath = os.path.join(self._programDirectory, "logo.png")
1603 logo = pixbuf_new_from_file(logoPath)
1604 dialog.set_logo(logo)
1605
1606 dialog.set_program_name(PROGRAM_NAME)
1607 dialog.set_version(const.VERSION)
1608 dialog.set_copyright("(c) 2012 by István Váradi")
1609 dialog.set_website("http://mlx.varadiistvan.hu")
1610 dialog.set_website_label(xstr("about_website"))
1611
1612 isHungarian = getLanguage()=="hu"
1613 authors = []
1614 for (familyName, firstName, role) in GUI._authors:
1615 author = "%s %s" % \
1616 (familyName if isHungarian else firstName,
1617 firstName if isHungarian else familyName)
1618 role = xstr("about_role_" + role)
1619 authors.append(author + " (" + role + ")")
1620 dialog.set_authors(authors)
1621
1622 dialog.set_license(xstr("about_license"))
1623
1624 if not pygobject:
1625 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1626
1627 self._aboutDialog = dialog
1628
1629 return self._aboutDialog
1630
1631 def _showAboutURL(self, dialog, link, user_data):
1632 """Show the about URL."""
1633 webbrowser.open(url = link, new = 1)
1634
1635 def _setTakeoffAntiIceOn(self, value):
1636 """Set the anti-ice on indicator."""
1637 self._wizard.takeoffAntiIceOn = value
1638
1639 def _setLandingAntiIceOn(self, value):
1640 """Set the anti-ice on indicator."""
1641 self._wizard.landingAntiIceOn = value
1642
1643 def _getCredentialsCallback(self):
1644 """Called when the web handler asks for the credentials."""
1645 # FIXME: this is almost the same as
1646 # SimBriefSetupPage._getCredentialsCallback
1647 with self._credentialsCondition:
1648 self._credentialsAvailable = False
1649
1650 gobject.idle_add(self._getCredentials)
1651
1652 while not self._credentialsAvailable:
1653 self._credentialsCondition.wait()
1654
1655 return (self._credentialsUserName, self._credentialsPassword)
1656
1657 def _getCredentials(self):
1658 """Get the credentials."""
1659 # FIXME: this is almost the same as
1660 # SimBriefSetupPage._getCredentials
1661 with self._credentialsCondition:
1662 config = self.config
1663
1664 dialog = CredentialsDialog(self, config.pilotID, config.password,
1665 xstr("login_title"),
1666 xstr("button_cancel"),
1667 xstr("button_ok"),
1668 xstr("label_pilotID"),
1669 xstr("login_pilotID_tooltip"),
1670 xstr("label_password"),
1671 xstr("login_password_tooltip"),
1672 xstr("login_info"),
1673 config.rememberPassword,
1674 xstr("remember_password"),
1675 xstr("login_remember_tooltip"))
1676 response = dialog.run()
1677
1678 if response==RESPONSETYPE_OK:
1679 self._credentialsUserName = dialog.userName
1680 self._credentialsPassword = dialog.password
1681 rememberPassword = dialog.rememberPassword
1682
1683 config.pilotID = self._credentialsUserName
1684
1685 config.password = \
1686 self._credentialsPassword if rememberPassword else ""
1687 config.rememberPassword = rememberPassword
1688
1689 config.save()
1690 else:
1691 self._credentialsUserName = None
1692 self._credentialsPassword = None
1693
1694 self._credentialsAvailable = True
1695 self._credentialsCondition.notify()
Note: See TracBrowser for help on using the repository browser.