source: src/mlx/gui/gui.py@ 1185:a48a0883c767

python3
Last change on this file since 1185:a48a0883c767 was 1181:52dda2b6d0eb, checked in by István Váradi <ivaradi@…>, 4 months ago

The Trac server can be accessed with password authentication only

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