source: src/mlx/gui/gui.py@ 482:79982e69f93b

Last change on this file since 482:79982e69f93b was 482:79982e69f93b, checked in by István Váradi <ivaradi@…>, 11 years ago

Added menu option to send a bug report (re #190)

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