source: src/mlx/gui/gui.py@ 996:8035d80d5feb

python3
Last change on this file since 996:8035d80d5feb was 996:8035d80d5feb, checked in by István Váradi <ivaradi@…>, 5 years ago

Using 'Gtk' instead of 'gtk' (re #347)

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