source: src/mlx/gui/gui.py@ 855:c1d387ee73d6

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

The PIREP message (if any) is displayed for an accepted flight (re #307)

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